Re: WebBluetooth BLE GATT client API

239 views
Skip to first unread message

Ben Tian

unread,
Sep 2, 2014, 6:05:48 AM9/2/14
to dev-w...@lists.mozilla.org, Eric Chou, Shawn Huang, Jamin Liu, Jocelyn Liu, Paul Theriault
Hi all,

It seems everyone agrees on current GATT API draft [1]. Our next step is to start implementing the GATT client API on Firefox OS (bug 933357 on bugzilla) and finish it by Oct 20. Feel free to feedback us on the API draft anytime. Thanks.

[1] https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2

-Ben

----- 原始郵件 -----

寄件者: "Ben Tian" <bt...@mozilla.com>
收件者: dev-w...@lists.mozilla.org
副本: "Eric Chou" <ec...@mozilla.com>, "Shawn Huang" <shu...@mozilla.com>, "Jamin Liu" <ja...@mozilla.com>, "Jocelyn Liu" <jo...@mozilla.com>, "Paul Theriault" <pau...@mozilla.com>
寄件備份: 2014 8 月 25 星期一 下午 10:48:37
主旨: WebBluetooth BLE GATT client API

Hi all,

We bluetooth team have drafted WebBluetooth API [1] for bluetooth low energy (BLE) GATT client functionality, and hope to obtain your valuable feedback.

General Attribute Profile (GATT) client API allows apps to connect nearby LE devices (e.g., heart rate sensor) and receive data via bluetooth [2]. As more and more apps require BLE functionality, we decide to expose GATT client API on Firefox OS 2.2 first and GATT server API on later release. The GATT client API is based on prior refinement [3] and includes following additional attribute, methods, and interfaces:


* Attribute


* BluetoothDevice.type
*
Methods

* BluetoothAdapter.startLeScan
* BluetoothAdapter.stopLeScan
* BluetoothDevice.connectGatt
*
Interfaces

* BluetoothGatt
* BluetoothGattService
* BluetoothGattCharacteristic
* BluetoothGattDescriptor
* BluetoothLeDeviceEvent
* BluetoothGattCharacteristicEvent

Note an WebBluetooth proposal by Google [4] is under discussion in W3C, but we'll introduce our own version of API [1] for privileged apps of Firefox OS first. The reason is that [4] still has some issues to discuss [5] and its feasibility on Firefox OS requires further assessment. We bluetooth team are participating in W3C discussion and will follow the final standardized version of API.

[1] https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2
[2] http://en.wikipedia.org/wiki/Bluetooth_low_energy#Software_model
[3] https://groups.google.com/forum/#!topic/mozilla.dev.webapi/SSVxpdq_lfQ
[4] https://webbluetoothcg.github.io/web-bluetooth/
[5] https://github.com/WebBluetoothCG/web-bluetooth/issues

-Ben

--
Ben Tian
Senior Software Engineer, Mozilla Taiwan

_______________________________________________
dev-webapi mailing list
dev-w...@lists.mozilla.org
https://lists.mozilla.org/listinfo/dev-webapi

jra...@logitech.com

unread,
Sep 9, 2014, 7:54:44 AM9/9/14
to mozilla-d...@lists.mozilla.org
Hi Ben,

I have a couple of comments, please stay tuned.

Cheers,
Julien

Ehsan Akhgari

unread,
Sep 9, 2014, 1:26:25 PM9/9/14
to Ben Tian, dev-w...@lists.mozilla.org, Eric Chou, Shawn Huang, Jamin Liu, Jocelyn Liu, Paul Theriault
Sorry for the delay, Ben. Working through my dev-webapi backlog at the
moment...

On 2014-08-25, 10:48 AM, Ben Tian wrote:
> Hi all,
>
> We bluetooth team have drafted WebBluetooth API [1] for bluetooth low energy (BLE) GATT client functionality, and hope to obtain your valuable feedback.
>
> General Attribute Profile (GATT) client API allows apps to connect nearby LE devices (e.g., heart rate sensor) and receive data via bluetooth [2]. As more and more apps require BLE functionality, we decide to expose GATT client API on Firefox OS 2.2 first and GATT server API on later release. The GATT client API is based on prior refinement [3] and includes following additional attribute, methods, and interfaces:
>
>
> * Attribute
>
>
> * BluetoothDevice.type
> *
> Methods
>
> * BluetoothAdapter.startLeScan

Do we only recognize the standardized service uuids here? If yes, you
might want to define an enum so that you don't have to check the uuids
manually in the implementation.

> * BluetoothAdapter.stopLeScan
> * BluetoothDevice.connectGatt

I think the connectGatt method as currently specified is wrong.

The prose says that it creates a *new* BluetoothGatt object, and assigns
it to the gatt property, and resolves the promise with it once the
connection is established. Here are some questions:

1. What happens if you call this method twice? Will the gatt property
get overridden by the second BluetoothGatt object?
2. What is the use case for accessing the BluetoothGatt property before
the connection is established?
3. If there is a use case in #2, we should probably resolve the promise
to the BluetoothGatt object, and just rely on the caller to call
connect() on it. Then we can remove the gatt property.
4. Otherwise, we should remove the gatt property, and remove the
connect() method on BluetoothGatt as well.

> *
> Interfaces
>
> * BluetoothGatt

Is the BluetoothGattService object returned from findService guaranteed
to be available synchronously?

> * BluetoothGattService

Ditto for findCharactersitic().

> * BluetoothGattCharacteristic

Please use WebIDL enums, not numeric enums.

Is there any reason for having the "value" property?

And same question about findDescriptor() as above.

> * BluetoothGattDescriptor

Same comment about enums.

Also, again I don't understand why |value| is useful.

> * BluetoothLeDeviceEvent

When can |device| be null here?

> * BluetoothGattCharacteristicEvent
>
> Note an WebBluetooth proposal by Google [4] is under discussion in W3C, but we'll introduce our own version of API [1] for privileged apps of Firefox OS first. The reason is that [4] still has some issues to discuss [5] and its feasibility on Firefox OS requires further assessment. We bluetooth team are participating in W3C discussion and will follow the final standardized version of API.
>
> [1] https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2
> [2] http://en.wikipedia.org/wiki/Bluetooth_low_energy#Software_model
> [3] https://groups.google.com/forum/#!topic/mozilla.dev.webapi/SSVxpdq_lfQ
> [4] https://webbluetoothcg.github.io/web-bluetooth/
> [5] https://github.com/WebBluetoothCG/web-bluetooth/issues

Cheers,
Ehsan

Jeffrey Yasskin

unread,
Sep 9, 2014, 2:37:25 PM9/9/14
to Ehsan Akhgari, Eric Chou, Shawn Huang, Jocelyn Liu, dev-w...@lists.mozilla.org, Ben Tian, Paul Theriault, Jamin Liu
I'm not on the Mozilla team, but I'll reply below with some of my own
thoughts about your questions.
It depends on your implementation. It's totally plausible to discover
all Services, Characteristics, and Descriptors during the connection
process, in which case all the find*() methods can return
synchronously.

On the other hand, it's also plausible to discover these entities
lazily, when they're first needed. This may save some power on both
devices, since less data may need to cross the radio. Requiring a
synchronous return precludes this potential optimization. On the other
hand, the "optimization" may wind up costing power if the application
accesses services in a pattern that confuses the browser logic.

The W3C proposal currently returns Promises from these methods, but we
can change that if implementers think the optimization isn't worth
supporting.

>> * BluetoothGattService
>
>
> Ditto for findCharactersitic().
>
>> * BluetoothGattCharacteristic
>
>
> Please use WebIDL enums, not numeric enums.

Note that these are bitfields intended to represent a set of boolean
options, rather than enumerations representing a single choice. If we
(including myself for http://webbluetoothcg.github.io/web-bluetooth/)
switch to string names, we should probably do it as a dictionary with
boolean values rather than an enum.

Arguing with myself:
1. The PROPERTY_* values here are defined in the Bluetooth spec
(https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=282159),
in section 3.G.3.3.1.1. In cases like the standardized UUIDs, it'd be
nice to match the Bluetooth numbers so that programs using the library
can update to newer versions of the Bluetooth spec without needing the
Web Bluetooth spec to change to match.
2. On the other hand, the PROPERTY_* values fill up their byte, so the
Bluetooth spec can't ever extend them.
3. However, Bluetooth currently defines two "extended" properties in
3.G.3.3.3.1, and it'd be nice to include those in the
Javascript-exposed set.
4. If we do expose the extended set, should we worry about how to
support new properties in future Bluetooth specs, or should we just
assume the web spec can keep up?

Given the above, I'm leaning toward defining these as a boolean
dictionary instead of a bit field.

The PERMISSION_* and WRITE_TYPE_* values are less clear. I don't think
they're defined anywhere in Bluetooth. Permissions aren't discoverable
from the Client (usually Central) side: you just have to try the
operation and see what error you get back. Permissions are useful on
the Server (usually Peripheral) side, but they can't be readonly in
that case. (e.g.
https://developer.apple.com/library/ios/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/PerformingCommonPeripheralRoleTasks/PerformingCommonPeripheralRoleTasks.html#//apple_ref/doc/uid/TP40013257-CH4-SW10)

The write type interacts with the Characteristic Properties
(WRITE_NO_RESPONSE, WRITE, and SIGNED_WRITE). For Characteristics that
support more than one of these, WRITE_TYPE_* could be useful as an
optional argument to writeValue(), but I don't see that in the Mozilla
API spec.

Permissions should probably be a dictionary like Properties. If
WRITE_TYPE is intended to control how a particular writeValue()
operation works, it needs to be an enum rather than a bit field.

> Is there any reason for having the "value" property?

Including 'value' allows
https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#oncharacteristicchanged
to pass just the characteristic when a change is notified, rather than
having to pass both the characteristic and its value. (Calling
readValue() redundantly in that event handler would waste radio and
might fail entirely because not all notifiable characteristics are
readable.) I'm not sure that's enough benefit to justify the property
and the risk of stale reads that it implies.

The cached value is also used in the case of reliable writes, where
the Server echoes the written value, and the Client is expected to
check that against what it just wrote. However, that will likely be
handled inside the Browser without needing Javascript to be involved.

> And same question about findDescriptor() as above.
>
>> * BluetoothGattDescriptor
>
>
> Same comment about enums.
>
> Also, again I don't understand why |value| is useful.
>
>> * BluetoothLeDeviceEvent
>
>
> When can |device| be null here?
>
>> * BluetoothGattCharacteristicEvent
>>
>> Note an WebBluetooth proposal by Google [4] is under discussion in W3C,
>> but we'll introduce our own version of API [1] for privileged apps of
>> Firefox OS first. The reason is that [4] still has some issues to discuss
>> [5] and its feasibility on Firefox OS requires further assessment. We
>> bluetooth team are participating in W3C discussion and will follow the final
>> standardized version of API.
>>
>> [1] https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2
>> [2] http://en.wikipedia.org/wiki/Bluetooth_low_energy#Software_model
>> [3] https://groups.google.com/forum/#!topic/mozilla.dev.webapi/SSVxpdq_lfQ
>> [4] https://webbluetoothcg.github.io/web-bluetooth/
>> [5] https://github.com/WebBluetoothCG/web-bluetooth/issues
>
>
> Cheers,
> Ehsan
>
>

Ehsan Akhgari

unread,
Sep 9, 2014, 3:53:03 PM9/9/14
to Jeffrey Yasskin, Eric Chou, Shawn Huang, Jocelyn Liu, dev-w...@lists.mozilla.org, Ben Tian, Paul Theriault, Jamin Liu
I think that given the above, it's a mistake to design the API
synchronously.

>>> * BluetoothGattService
>>
>>
>> Ditto for findCharactersitic().
>>
>>> * BluetoothGattCharacteristic
>>
>>
>> Please use WebIDL enums, not numeric enums.
>
> Note that these are bitfields intended to represent a set of boolean
> options, rather than enumerations representing a single choice. If we
> (including myself for http://webbluetoothcg.github.io/web-bluetooth/)
> switch to string names, we should probably do it as a dictionary with
> boolean values rather than an enum.

Hmm, that seems more JS like.
I think there is an argument to be made for making this API easier for
JS progreammers who may not be familiar with the Bluetooth spec, etc.
However, there are other examples of using these C-style numeric enums
in the platform too, so the situation is definitely not clear-cut.
However, most modern APIs attempt to do things "the JS way."

>> Is there any reason for having the "value" property?
>
> Including 'value' allows
> https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#oncharacteristicchanged
> to pass just the characteristic when a change is notified, rather than
> having to pass both the characteristic and its value. (Calling
> readValue() redundantly in that event handler would waste radio and
> might fail entirely because not all notifiable characteristics are
> readable.) I'm not sure that's enough benefit to justify the property
> and the risk of stale reads that it implies.

Why can't the value live on the event though?

> The cached value is also used in the case of reliable writes, where
> the Server echoes the written value, and the Client is expected to
> check that against what it just wrote. However, that will likely be
> handled inside the Browser without needing Javascript to be involved.

I'm having difficulty understanding this bit (I'm not familiar with the
Bluetooth spec.)

Jeffrey Yasskin

unread,
Sep 9, 2014, 4:26:18 PM9/9/14
to Ehsan Akhgari, Eric Chou, Shawn Huang, Jocelyn Liu, dev-webapi, Ben Tian, Paul Theriault, Jamin Liu
I think it can. You'd have
https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGattCharacteristicEvent
include both a 'characteristic' field and a 'value' field.
Alternately, I've been considering having those events dispatched to
the Characteristic object itself and bubble up to the equivalent of
BluetoothGatt, so the 'target' field would point to the
Characteristic. We'd still want the 'value' field on the event if it's
not on the Characteristic.

A downside of dispatching the event to the Characteristic object would
be that it could make garbage-collecting the Characteristics hard. We
might have to specify when the "active" characteristic object is
replaced. e.g. when the page is unloaded? when the application
explicitly disconnects? when the device loses its connection by moving
out of range, but then moves back into range and auto-reconnects? But
we might have to specify that anyway.

>> The cached value is also used in the case of reliable writes, where
>> the Server echoes the written value, and the Client is expected to
>> check that against what it just wrote. However, that will likely be
>> handled inside the Browser without needing Javascript to be involved.
>
>
> I'm having difficulty understanding this bit (I'm not familiar with the
> Bluetooth spec.)

Sorry. This is the "Reliable Writes" algorithm in section 3.G.4.9.5 of
https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=282159
(BT4.1). That's defined in terms of Attribute Protocol messages
defined in 3.F.3.4.6, although you probably don't need to understand
those well. The application would call writeValue(big_array_buffer),
and the Browser needs to save that value through the whole sequence of
'Prepare Write Request' and 'Prepare Write Response' messages so that
it can validate the data in the Response and decide whether to commit
the write or abort it. But again, I think that's all internal to the
implementation and doesn't really need to be exposed to Javascript as
a 'value' field on the Characteristic.

It's also possible I'm missing another use for the 'value' field that
does need it to be exposed.

Jeffrey Yasskin

unread,
Sep 9, 2014, 5:26:24 PM9/9/14
to Ehsan Akhgari, Eric Chou, Shawn Huang, Jocelyn Liu, dev-w...@lists.mozilla.org, Ben Tian, Paul Theriault, Jamin Liu
I've filed https://github.com/WebBluetoothCG/web-bluetooth/issues/36
to discuss this change for the W3C proposal. It turned out that the
W3C proposal currently uses an array of strings to represent the set
(and omits the list of possible strings; oops).

>>> Is there any reason for having the "value" property?
>>
>>
>> Including 'value' allows
>>
>> https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#oncharacteristicchanged
>> to pass just the characteristic when a change is notified, rather than
>> having to pass both the characteristic and its value. (Calling
>> readValue() redundantly in that event handler would waste radio and
>> might fail entirely because not all notifiable characteristics are
>> readable.) I'm not sure that's enough benefit to justify the property
>> and the risk of stale reads that it implies.
>
>
> Why can't the value live on the event though?
>
>> The cached value is also used in the case of reliable writes, where
>> the Server echoes the written value, and the Client is expected to
>> check that against what it just wrote. However, that will likely be
>> handled inside the Browser without needing Javascript to be involved.
>
>
> I'm having difficulty understanding this bit (I'm not familiar with the
> Bluetooth spec.)
>
>

Ben Tian

unread,
Sep 10, 2014, 6:13:14 AM9/10/14
to Jeffrey Yasskin, Eric Chou, Shawn Huang, Ehsan Akhgari, Jocelyn Liu, dev-webapi, Paul Theriault, Jamin Liu
Hi Ehsan/Jeffrey,

Thanks for your questions and feedbacks. Please see my response below.

===


Q1: (BluetoothAdapter.startLeScan) Do we only recognize the standardized service uuids here?




No, not only the standardized service uuids but also customized ones. This allows applications for customized services and hardwares.





Q2: (BluetoothDevice.connectGatt) The prose says that it creates a *new* BluetoothGatt object, and assigns it to the gatt property, and resolves the promise with it once the connection is established. Here are some questions:

1. What happens if you call this method twice? Will the gatt property get overridden by the second BluetoothGatt object?




It should check whether a BluetoothGatt object already exists before creating a new one. If yes it would reuse the existing one. I'll revise the prose.




Q3: 2. What is the use case for accessing the BluetoothGatt property before the connection is established?




They would be default values: an empty array [1] and BluetoothConnectionState.disconnected [2]. I'll add explanation for this.

[1] https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#services

[2] https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#connectionState




Q4: 3. If there is a use case in #2, we should probably resolve the promise to the BluetoothGatt object, and just rely on the caller to call connect() on it. Then we can remove the gatt property.

4. Otherwise, we should remove the gatt property, and remove the connect() method on BluetoothGatt as well.




We don't expect the use case in #2. However BluetoothDevice.connectGatt() differs from BluetoothGatt.connect() as the former additionally discovers services offered by remote LE devices [3][4].




The scenario is as following:

1. Apps call BluetoothDevice.connectGatt() to create gatt object, connect to remote device, and discover its offered services.

2. When the remote device goes out-of-range, the connection is dropped.

3. When the remote device comes back in range (and |aAutoConnect| was set false [3]), apps have to call BluetoothGatt.connect() to reconnect to the remote device. The reconnection does NOT rediscover services but only uses previously cached ones.




[3] https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothDevice#connectGatt.28boolean_aAutoConnect.29

[4] https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#connect.28.29




Q5: Is the BluetoothGattService object returned from findService guaranteed to be available synchronously?

Jeffrey: It depends on your implementation. It's totally plausible to discover all Services, Characteristics, and Descriptors during the connection process, in which case all the find*() methods can return synchronously.




This is the way we'd like to implement. That's why we make the method synchronous.




Jeffery: On the other hand, it's also plausible to discover these entities lazily, when they're first needed. This may save some power on both devices, since less data may need to cross the radio. Requiring a synchronous return precludes this potential optimization. On the other hand, the "optimization" may wind up costing power if the application accesses services in a pattern that confuses the browser logic.




The W3C proposal currently returns Promises from these methods, but we can change that if implementers think the optimization isn't worth supporting.




Q6: I think that given the above, it's a mistake to design the API synchronously.




Why? It's the decision of implementation, right?




Q7: (BluetoothGattCharacteristic) Please use WebIDL enums, not numeric enums.

Jeffery: Note that these are bitfields intended to represent a set of boolean options, rather than enumerations representing a single choice. If we (including myself for http://webbluetoothcg.github.io/web-bluetooth/ ) switch to string names, we should probably do it as a dictionary with boolean values rather than an enum.




Boolean dictionary seems a suitable option that we didn’t consider for PROPERTY_*, PERMISSION_*, and WRITE_TYPE_*. Let us discuss internally and bring back some conclusion.




Jeffery: The write type interacts with the Characteristic Properties (WRITE_NO_RESPONSE, WRITE, and SIGNED_WRITE). For Characteristics that support more than one of these, WRITE_TYPE_* could be useful as an optional argument to writeValue(), but I don't see that in the Mozilla API spec. Permissions should probably be a dictionary like Properties. If WRITE_TYPE is intended to control how a particular writeValue() operation works, it needs to be an enum rather than a bit field.




We don’t make WRITE_TYPE_* an optional argument since we think it lasts through writeValue() operations instead of a one-time setting for a particular writeValue() operation. Also I prefer boolean dictionary for WRITE_TYPE_* since enum values cannot be combined for Characteristics that support more than one of WRITE_TYPE_*.




Q8: Is there any reason for having the "value" property?

Jeffrey: Including 'value' allows https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#oncharacteristicchanged to pass just the characteristic when a change is notified, rather than having to pass both the characteristic and its value. (Calling readValue() redundantly in that event handler would waste radio and might fail entirely because not all notifiable characteristics are readable.) I'm not sure that's enough benefit to justify the property and the risk of stale reads that it implies.




BluetoothGattCharacteristic.value is the cached value of the characteristic. Having “value” property so that apps don’t have to always query remote device (i.e., call BluetoothGattCharacteristic.readValue()) for the characteristic’s value.




Q9: (BluetoothGatt) Reliable Writes

Jeffrey: Sorry. This is the "Reliable Writes" algorithm in section 3.G.4.9.5 of

https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=282159

(BT4.1). That's defined in terms of Attribute Protocol messages defined in 3.F.3.4.6, although you probably don't need to understand those well. The application would call writeValue(big_array_buffer), and the Browser needs to save that value through the whole sequence of 'Prepare Write Request' and 'Prepare Write Response' messages so that it can validate the data in the Response and decide whether to commit the write or abort it. But again, I think that's all internal to the implementation and doesn't really need to be exposed to Javascript as a 'value' field on the Characteristic.




Thanks Jeffrey for the explanation of “Reliable Writes”. One thing to mention is that we DO expect JS apps to verify the values to write [5].




Think “Reliable Writes” as transaction of a series of writeValue() operation. The scenario is as following:

1. Apps calls BluetoothGatt.beginReliableWrite() to initiate reliable write.

2. For each subsequent BluetoothCharacteristic.writeValue() call, apps have to validate whether the value to write (input parameter “value” [6]) is identical to the value in the Response (returned from promise [6]). If not, apps have to call BluetoothGatt.abortReliableWrite() to cancel current transaction; if all succeed, these writes are queued up in remote LE device.

3. Apps call BluetoothGatt.executeReliableWrite() to commit all queued writes.




[5] https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#beginReliableWrite.28.29

[6] https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGattCharacteristic#writeValue.28ArrayBuffer_value.29




Q10: (BluetoothLeDeviceEvent) When can |device| be null here?




No, it shouldn’t. That was a typo and I’ve removed the “?” already.

===

-Ben


Ehsan Akhgari

unread,
Sep 10, 2014, 2:29:42 PM9/10/14
to Ben Tian, Jeffrey Yasskin, Eric Chou, Shawn Huang, Jocelyn Liu, dev-webapi, Paul Theriault, Jamin Liu
On 2014-09-10, 6:00 AM, Ben Tian wrote:
> Hi Ehsan/Jeffrey,
>
> Thanks for your questions and feedbacks. Please see my response below.
>
> ===
>
> *Q1: (BluetoothAdapter.startLeScan) Do we only recognize the
> standardized service uuids here?*
>
>
> No, not only the standardized service uuids but also customized ones.
> This allows applications for customized services and hardwares.

OK, fair enough.

> *Q2: (BluetoothDevice.connectGatt) The prose says that it creates a
> *new* BluetoothGatt object, and assigns it to the gatt property, and
> resolves the promise with it once the connection is established. Here
> are some questions:*
>
> * 1. What happens if you call this method twice? Will the gatt
> property get overridden by the second BluetoothGatt object?*
>
>
> It should check whether a BluetoothGatt object already exists before
> creating a new one. If yes it would reuse the existing one. *I'll revise
> the prose.*

Having this method do different things depending on whether it has been
called before is really suboptimal. Please don't do that!

> *Q3: 2. What is the use case for accessing the BluetoothGatt property
> before the connection is established?*
>
>
> They would be default values: an empty array [1] and
> BluetoothConnectionState.disconnected [2]. *I'll add explanation for this.*
> *Q4: 3. If there is a use case in #2, we should probably resolve the
> promise to the BluetoothGatt object, and just rely on the caller to call
> connect() on it. Then we can remove the gatt property.*
>
> * 4. Otherwise, we should remove the gatt property, and remove the
> connect() method on BluetoothGatt as well.*
>
>
> We don't expect the use case in #2. However
> BluetoothDevice.connectGatt() differs from BluetoothGatt.connect() as
> the former additionally discovers services offered by remote LE devices
> [3][4].
>
>
> The scenario is as following:
>
> 1. Apps call BluetoothDevice.connectGatt() to create gatt object,
> connect to remote device, and discover its offered services.
>
> 2. When the remote device goes out-of-range, the connection is dropped.
>
> 3. When the remote device comes back in range (and |aAutoConnect| was
> set false [3]), apps have to call BluetoothGatt.connect() to reconnect
> to the remote device. The reconnection does NOT rediscover services but
> only uses previously cached ones.
>
>
> [3]
> https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothDevice#connectGatt.28boolean_aAutoConnect.29
>
> [4]
> https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#connect.28.29

This semantic is really awkward, and I think we can do much better.
Please consider the following counter proposal (based on my poor
understanding of what we want to achieve here, please suggest corrections!)

* BluetoothDevice will have a gatt property, which is always available.
That is just a JS object, and accessing that property doesn't initiate
any Bluetooth activity.

* Add a discover() method or some such that will start the discovery
process, which returns a promise resolving to void (or something
sensible) once the discovery is finished.

* Have the promise returned by connect() rejected if the service
discovery has not been performed yet.

* Remove the connectGatt() method altogether.

Here is how the API would be used:

device.gatt.discover().then(() => {
device.gatt.connect().then(() => {
// do something here
})
})

> *Q5: Is the BluetoothGattService object returned from findService
> guaranteed to be available synchronously?*
>
> Jeffrey: It depends on your implementation. It's totally plausible to
> discover all Services, Characteristics, and Descriptors during the
> connection process, in which case all the find*() methods can return
> synchronously.
>
>
> This is the way we'd like to implement. That's why we make the method
> synchronous.

That wasn't really my question. My question was, is this guaranteed to
be available synchronously on all implementations, always? If not, we
should make the method asynchronous *even* if we can implement it
synchronously right now.

> Jeffery: On the other hand, it's also plausible to discover these
> entities lazily, when they're first needed. This may save some power on
> both devices, since less data may need to cross the radio. Requiring a
> synchronous return precludes this potential optimization. On the other
> hand, the "optimization" may wind up costing power if the application
> accesses services in a pattern that confuses the browser logic.

And FWIW, this is an example of a scenario where this won't be available
synchronously.

> The W3C proposal currently returns Promises from these methods, but we
> can change that if implementers think the optimization isn't worth
> supporting.
>
>
> *Q6: I think that given the above, it's a mistake to design the API
> synchronously.*
>
>
> Why? It's the decision of implementation, right?

No it's not. By making the *API* synchronous, you force all future
implementations of the same API to be synchronous as well. I understand
that this is a certified API which we can at least in theory change at
will, but proper API designs involves thinking about implementation
issues beyond what the implementer of the API in one engine tends to
think about.

> *Q7: (BluetoothGattCharacteristic) Please use WebIDL enums, not numeric
> enums.*
>
> Jeffery: Note that these are bitfields intended to represent a set of
> boolean options, rather than enumerations representing a single choice.
> If we (including myself for
> http://webbluetoothcg.github.io/web-bluetooth/) switch to string names,
> we should probably do it as a dictionary with boolean values rather than
> an enum.
>
>
> Boolean dictionary seems a suitable option that we didn’t consider for
> PROPERTY_*, PERMISSION_*, and WRITE_TYPE_*. Let us discuss internally
> and bring back some conclusion.

Sounds good!

> Jeffery: The write type interacts with the Characteristic Properties
> (WRITE_NO_RESPONSE, WRITE, and SIGNED_WRITE). For Characteristics that
> support more than one of these, WRITE_TYPE_* could be useful as an
> optional argument to writeValue(), but I don't see that in the Mozilla
> API spec. Permissions should probably be a dictionary like Properties.
> If WRITE_TYPE is intended to control how a particular writeValue()
> operation works, it needs to be an enum rather than a bit field.
>
>
> We don’t make WRITE_TYPE_* an optional argument since we think it lasts
> through writeValue() operations instead of a one-time setting for a
> particular writeValue() operation. Also I prefer boolean dictionary for
> WRITE_TYPE_* since enum values cannot be combined for Characteristics
> that support more than one of WRITE_TYPE_*.
>
>
> *Q8: Is there any reason for having the "value" property?*
>
> Jeffrey: Including 'value' allows
> https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#oncharacteristicchanged
> to pass just the characteristic when a change is notified, rather than
> having to pass both the characteristic and its value. (Calling
> readValue() redundantly in that event handler would waste radio and
> might fail entirely because not all notifiable characteristics are
> readable.) I'm not sure that's enough benefit to justify the property
> and the risk of stale reads that it implies.
>
>
> BluetoothGattCharacteristic.value is the cached value of the
> characteristic. Having “value” property so that apps don’t have to
> always queryremote device (i.e., call
> BluetoothGattCharacteristic.readValue()) for the characteristic’s value.

The apps can cache the value property themselves if they want to. These
types of weird cache only properties don't have any prior art. In
general, if something is as simple as this (by setting an expando
property) to implement in the application code in JS, we should try to
avoid adding it to the API.

> *Q9: (BluetoothGatt) Reliable Writes*
Ah I think I understand this part now! This is basically some kind of a
transaction concept, is that correct?

> *Q10: (BluetoothLeDeviceEvent) When can |device| be null here?*
>
>
> No, it shouldn’t. That was a typo and I’ve removed the “?” already.

Great, thanks!

Cheers,
Ehsan

> ------------------------------------------------------------------------
> *寄件者: *"Jeffrey Yasskin" <jyas...@google.com>
> *收件者: *"Ehsan Akhgari" <ehsan....@gmail.com>
> *副本: *"Ben Tian" <bt...@mozilla.com>, "dev-webapi"
> <dev-w...@lists.mozilla.org>, "Eric Chou" <ec...@mozilla.com>, "Shawn
> Huang" <shu...@mozilla.com>, "Jamin Liu" <ja...@mozilla.com>, "Jocelyn
> Liu" <jo...@mozilla.com>, "Paul Theriault" <pau...@mozilla.com>
> *寄件備份: *2014 9 月 10 星期三 上午 4:26:18
> *主旨: *Re: WebBluetooth BLE GATT client API
> >>> The prose says that it creates a *new* BluetoothGatt object, and
> assigns
> >>> it
> >>> to the gatt property, and resolves the promise with it once the
> >>> connection
> >>> is established. Here are some questions:
> >>>
> >>> 1. What happens if you call this method twice? Will the gatt property
> >>> get
> >>> overridden by the second BluetoothGatt object?
> >>> 2. What is the use case for accessing the BluetoothGatt property before
> >>> the
> >>> connection is established?
> >>> 3. If there is a use case in #2, we should probably resolve the promise
> >>> to
> >>> the BluetoothGatt object, and just rely on the caller to call connect()
> >>> on
> >>> it. Then we can remove the gatt property.
> >>> 4. Otherwise, we should remove the gatt property, and remove the
> >>> connect()
> >>> method on BluetoothGatt as well.
> >>>
> >>>> *
> >>>> Interfaces
> >>>>
> >>>> * BluetoothGatt
> >>>
> >>>
> >>>
> >>> Is the BluetoothGattService object returned from findService guaranteed
> >>> to
> >>> be available synchronously?
> >>
> >>
> >> It depends on your implementation. It's totally plausible to discover
> >> all Services, Characteristics, and Descriptors during the connection
> >> process, in which case all the find*() methods can return
> >> synchronously.
> >>
> >> On the other hand, it's also plausible to discover these entities
> >> lazily, when they're first needed. This may save some power on both
> >> devices, since less data may need to cross the radio. Requiring a
> >> synchronous return precludes this potential optimization. On the other
> >> hand, the "optimization" may wind up costing power if the application
> >> accesses services in a pattern that confuses the browser logic.
> >>
> >> The W3C proposal currently returns Promises from these methods, but we
> >> can change that if implementers think the optimization isn't worth
> >> supporting.
> >
> >
> > I think that given the above, it's a mistake to design the API
> > synchronously.
> >
> >>>> * BluetoothGattService
> >>>
> >>>
> >>>
> >>> Ditto for findCharactersitic().
> >>>
> >>>> * BluetoothGattCharacteristic
> >>>
> >>>
> >>>
> >>> Please use WebIDL enums, not numeric enums.
> >>
> >>
> >> Note that these are bitfields intended to represent a set of boolean
> >> options, rather than enumerations representing a single choice. If we
> >> (including myself for http://webbluetoothcg.github.io/web-bluetooth/)
> >> switch to string names, we should probably do it as a dictionary with
> >> boolean values rather than an enum.
> >
> >
> >> The write type interacts with the Characteristic Properties
> >> (WRITE_NO_RESPONSE, WRITE, and SIGNED_WRITE). For Characteristics that
> >> support more than one of these, WRITE_TYPE_* could be useful as an
> >> optional argument to writeValue(), but I don't see that in the Mozilla
> >> API spec.
> >>
> >> Permissions should probably be a dictionary like Properties. If
> >> WRITE_TYPE is intended to control how a particular writeValue()
> >> operation works, it needs to be an enum rather than a bit field.
> >
> >
> > I think there is an argument to be made for making this API easier for JS
> > progreammers who may not be familiar with the Bluetooth spec, etc.
> However,
> > there are other examples of using these C-style numeric enums in the
> > platform too, so the situation is definitely not clear-cut. However, most
> > modern APIs attempt to do things "the JS way."
> >
> >>> Is there any reason for having the "value" property?
> >>
> >>
> >> Including 'value' allows
> >>
> >>
> https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#oncharacteristicchanged
> >> to pass just the characteristic when a change is notified, rather than
> >> having to pass both the characteristic and its value. (Calling
> >> readValue() redundantly in that event handler would waste radio and
> >> might fail entirely because not all notifiable characteristics are
> >> readable.) I'm not sure that's enough benefit to justify the property
> >> and the risk of stale reads that it implies.
> >
> >
> > Why can't the value live on the event though?
>
> I think it can. You'd have
> https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGattCharacteristicEvent
> include both a 'characteristic' field and a 'value' field.
> Alternately, I've been considering having those events dispatched to
> the Characteristic object itself and bubble up to the equivalent of
> BluetoothGatt, so the 'target' field would point to the
> Characteristic. We'd still want the 'value' field on the event if it's
> not on the Characteristic.
>
> A downside of dispatching the event to the Characteristic object would
> be that it could make garbage-collecting the Characteristics hard. We
> might have to specify when the "active" characteristic object is
> replaced. e.g. when the page is unloaded? when the application
> explicitly disconnects? when the device loses its connection by moving
> out of range, but then moves back into range and auto-reconnects? But
> we might have to specify that anyway.
>
> >> The cached value is also used in the case of reliable writes, where
> >> the Server echoes the written value, and the Client is expected to
> >> check that against what it just wrote. However, that will likely be
> >> handled inside the Browser without needing Javascript to be involved.
> >
> >
> > I'm having difficulty understanding this bit (I'm not familiar with the
> > Bluetooth spec.)
>
> Sorry. This is the "Reliable Writes" algorithm in section 3.G.4.9.5 of
> https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=282159
> (BT4.1). That's defined in terms of Attribute Protocol messages
> defined in 3.F.3.4.6, although you probably don't need to understand
> those well. The application would call writeValue(big_array_buffer),
> and the Browser needs to save that value through the whole sequence of
> 'Prepare Write Request' and 'Prepare Write Response' messages so that
> it can validate the data in the Response and decide whether to commit
> the write or abort it. But again, I think that's all internal to the
> implementation and doesn't really need to be exposed to Javascript as
> a 'value' field on the Characteristic.
>
> It's also possible I'm missing another use for the 'value' field that
> does need it to be exposed.
>
> >>> And same question about findDescriptor() as above.
> >>>
> >>>> * BluetoothGattDescriptor
> >>>
> >>>
> >>>
> >>> Same comment about enums.
> >>>
> >>> Also, again I don't understand why |value| is useful.
> >>>
> >>>> * BluetoothLeDeviceEvent
> >>>
> >>>
> >>>
> >>> When can |device| be null here?
> >>>

Jeffrey Yasskin

unread,
Sep 10, 2014, 5:41:49 PM9/10/14
to Ehsan Akhgari, Eric Chou, Shawn Huang, Jocelyn Liu, dev-webapi, Ben Tian, Paul Theriault, Jamin Liu
One point here is that _device_ discovery is a different process from
_service_ discovery within a known device. I believe that the devices
need to be bluetooth-connected before service discovery can happen. I
haven't thought enough about the `gatt` field or non-GATT operation to
have an opinion on connect() vs connectGatt().

> * Remove the connectGatt() method altogether.
>
> Here is how the API would be used:
>
> device.gatt.discover().then(() => {
> device.gatt.connect().then(() => {
> // do something here
> })
> })
>

>> *Q7: (BluetoothGattCharacteristic) Please use WebIDL enums, not numeric
>> enums.*
>>
>> ...
>> Jeffrey: The write type interacts with the Characteristic Properties
>> (WRITE_NO_RESPONSE, WRITE, and SIGNED_WRITE). For Characteristics that
>> support more than one of these, WRITE_TYPE_* could be useful as an
>> optional argument to writeValue(), but I don't see that in the Mozilla
>> API spec. Permissions should probably be a dictionary like Properties.
>> If WRITE_TYPE is intended to control how a particular writeValue()
>> operation works, it needs to be an enum rather than a bit field.
>>
>>
>> We don't make WRITE_TYPE_* an optional argument since we think it lasts
>> through writeValue() operations instead of a one-time setting for a
>> particular writeValue() operation. Also I prefer boolean dictionary for
>> WRITE_TYPE_* since enum values cannot be combined for Characteristics
>> that support more than one of WRITE_TYPE_*.

Interesting. Could you explain how you intend developers to use the
WRITE_TYPE_* values? I only see
https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGattCharacteristic#writeType
using them, and it's readonly, so it seems redundant with these 3 bits
in https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGattCharacteristic#properties_2.

>> *Q8: Is there any reason for having the "value" property?*
>>
>> Jeffrey: Including 'value' allows
>>
>> https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#oncharacteristicchanged
>> to pass just the characteristic when a change is notified, rather than
>> having to pass both the characteristic and its value. (Calling
>> readValue() redundantly in that event handler would waste radio and
>> might fail entirely because not all notifiable characteristics are
>> readable.) I'm not sure that's enough benefit to justify the property
>> and the risk of stale reads that it implies.
>>
>>
>> BluetoothGattCharacteristic.value is the cached value of the
>> characteristic. Having "value" property so that apps don't have to
>> always queryremote device (i.e., call
>> BluetoothGattCharacteristic.readValue()) for the characteristic's value.
>
>
> The apps can cache the value property themselves if they want to. These
> types of weird cache only properties don't have any prior art. In general,
> if something is as simple as this (by setting an expando property) to
> implement in the application code in JS, we should try to avoid adding it to
> the API.

I've filed https://github.com/WebBluetoothCG/web-bluetooth/issues/37
to discuss this change for the W3C proposal.

>> *Q9: (BluetoothGatt) Reliable Writes*
>>
>>
>> Jeffrey: Sorry. This is the "Reliable Writes" algorithm in section
>> 3.G.4.9.5 of
>>
>> https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=282159
>>
>> (BT4.1). That's defined in terms of Attribute Protocol messages defined
>> in 3.F.3.4.6, although you probably don't need to understand those well.
>> The application would call writeValue(big_array_buffer), and the Browser
>> needs to save that value through the whole sequence of 'Prepare Write
>> Request' and 'Prepare Write Response' messages so that it can validate
>> the data in the Response and decide whether to commit the write or abort
>> it. But again, I think that's all internal to the implementation and
>> doesn't really need to be exposed to Javascript as a 'value' field on
>> the Characteristic.
>>
>>
>> Thanks Jeffrey for the explanation of "Reliable Writes". One thing to
>> mention is that we DO expect JS apps to verify the values to write [5].

Whoops, thanks for the correction. You _might_ be able to save
developers some work by validating the result yourself and rejecting
the promise if it's incorrect, but I don't know if that would actually
wind up being better.

>> Think "Reliable Writes" as transaction of a series of writeValue()
>> operation. The scenario is as following:
>>
>> ...
>
>
> Ah I think I understand this part now! This is basically some kind of a
> transaction concept, is that correct?

Yep. I should have used that word in the first place. :)

Jeffrey

Ben Tian

unread,
Sep 11, 2014, 6:24:16 AM9/11/14
to Jeffrey Yasskin, Eric Chou, Shawn Huang, Ehsan Akhgari, Jocelyn Liu, dev-webapi, Paul Theriault, Jamin Liu


Hi Ehsan/Jeffrey,




Please see my response below.




===

Q2: BluetoothDevice.connectGatt() and BluetoothDevice.gatt

>> It should check whether a BluetoothGatt object already exists before

>> creating a new one. If yes it would reuse the existing one. *I'll revise

>> the prose.*

>

> Having this method do different things depending on whether it has been

> called before is really suboptimal. Please don't do that!




I don’t get why it's suboptimal. BluetoothDevice.connectGatt() establishes only initial connection, so the method should be rejected if BluetoothDevice.gatt already exists, which means the initial connection has been established before. Instead apps should call BluetoothGatt.connect() to re-connect.




Q4: BluetoothDevice.connectGatt() vs BluetoothGatt.connect()

> This semantic is really awkward, and I think we can do much better. Please

> consider the following counter proposal (based on my poor understanding of

> what we want to achieve here, please suggest corrections!)

>

> * BluetoothDevice will have a gatt property, which is always available.

> That is just a JS object, and accessing that property doesn't initiate any

> Bluetooth activity.

>

> * Add a discover() method or some such that will start the discovery

> process, which returns a promise resolving to void (or something sensible)

> once the discovery is finished.

>

> * Have the promise returned by connect() rejected if the service discovery

> has not been performed yet.

>

> * Remove the connectGatt() method altogether.

>

> Here is how the API would be used:

>

> device.gatt.discover().then(() => {

> device.gatt.connect().then(() => {

> // do something here

> })

> })

>




We’ve considered discover() method but didn’t adopt because we want to discover services only when apps need: 1) during initial connection establishment and 2) services on remote server changed [1]. Exposing discover() method means apps are responsible for service discovery and they may call it anytime they want, resulting in costly power consumption for transmission. In addition, consider case 2), service (re)discovery is required when apps are notified of service changed [1]. Each app calls discover() to trigger service discovery multiple times, but in fact only once is required if OS does it before notifying apps. That’s why we finally decide not to expose discover() method to apps and keep the responsibility in OS.




[1] https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#onservicechanged




Q5: Is the BluetoothGattService object returned from findService guaranteed to be available synchronously?




> That wasn't really my question. My question was, is this guaranteed to be available

> synchronously on all implementations, always? If not, we should make the method

> asynchronous *even* if we can implement it synchronously right now.




I see. It seems to me asynchronous methods are always preferred since they cover synchronous cases. Do you have examples/guidelines to understand when synchronous methods are more suitable than asynchronous ones?




Q7: (BluetoothGattCharacteristic) Please use WebIDL enums, not numeric enums.

> …

> Interesting. Could you explain how you intend developers to use the WRITE_TYPE_* values? I

> only see https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGattCharacteristic#writeType using them, and it's readonly, so it seems redundant with

> these 3 bits in https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGattCharacteristic#properties_2.




Whoops, you’re right. We should make BluetoothGattCharacteristic.writeType set-able. I've corrected it.




Q8: Is there any reason for having the "value" property?

>> The apps can cache the value property themselves if they want to. These

>> types of weird cache only properties don't have any prior art. In general,

>> if something is as simple as this (by setting an expando property) to

>> implement in the application code in JS, we should try to avoid adding it to

>> the API.




Cache in JS + "value" property removal is worth discussing. One minor downside is that apps would have to associate values to characteristics and descriptors. Let us discuss internally first since other use cases and APIs (e.g., BluetoothCharacteristicEvent) are also involved.




Q9: (BluetoothGatt) Reliable Writes

> Whoops, thanks for the correction. You _might_ be able to save

> developers some work by validating the result yourself and rejecting

> the promise if it's incorrect, but I don't know if that would actually

> wind up being better.




It’s do-able but makes BluetoothCharacteristic.writeValue() behave inconsistently between normal writes and reliable writes: resolve in normal writes; reject in reliable writes if validation fails.

===

-Ben
----- 原始郵件 -----

寄件者: "Jeffrey Yasskin" <jyas...@google.com>
收件者: "Ehsan Akhgari" <ehsan....@gmail.com>
副本: "Ben Tian" <bt...@mozilla.com>, "dev-webapi" <dev-w...@lists.mozilla.org>, "Eric Chou" <ec...@mozilla.com>, "Shawn Huang" <shu...@mozilla.com>, "Jamin Liu" <ja...@mozilla.com>, "Jocelyn Liu" <jo...@mozilla.com>, "Paul Theriault" <pau...@mozilla.com>
寄件備份: 2014 9 月 11 星期四 上午 5:41:49
主旨: Re: WebBluetooth BLE GATT client API

Julien Racle

unread,
Sep 12, 2014, 6:05:16 AM9/12/14
to mozilla-d...@lists.mozilla.org
Hi Ben,

sorry for the delay.. I like overall design of the API, close to Android one (https://developer.android.com/reference/android/bluetooth/package-summary.html). FYI I've also begun posting in W3C issues (https://github.com/WebBluetoothCG/web-bluetooth/issues).

guys (Ben/Ehsan/Jeffrey), you already did already lots of great comments.

I'll start with Q2:(BluetoothDevice.connectGatt)

- It is in no way sub-optimal, as Ben says. This is a design decision.
I'm also aligned with Jefferey's comments around device discovery vs service discovery. Also, operations on GATT profile are available only when GAP established a connection.

- Now concerning design of BluetoothDevice and BluetoothGatt, it will work well as it is defined, no pb on that point.

2 things could be debated though:
1. the way we get BluetoothGatt (GAP connect/disconnect)
2. the way BluetoothGatt gives access to services (service discovery)

#1. 'connectGatt' gives (a singleton) BluetoothGatt instance. Fine for me. Calling disconnect() on this instance leads to GAP disconnecting it.
I also agree we could have a 'gatt' property on BluetoothDevice instances, which would be null if device is BT classic only. (We could also over-engineer a bit with a more generic access to profiles).
Then we could do bluetoothDevice.gatt.connect(), as we do for reconnection().
It adds a step but has the nice side to give more explicit behavior.

#2. I'm unclear about 'services' property vs 'findService' behavior. Seems that the property is filled, hence services are discovered automatically in a synchronous way. That for sure is convenient but "sub-optimal". I would also consider reflecting BLE spec more closely by:
- 1. using Promises: so that we can implement synchronously or asynchronously under the hood.
- 2. I would then change BluetoothGatt, removing 'findService'. I'll add:
Promise<sequence<BluetoothService>> getPrimaryServices() (could also be called discoverPrimaryServices()
Promise<BluetoothService> getPrimaryService(UUID)
I'd keep 'services' property, but make it lazily-initialized. It'd be updated upon gatt service changed of course, and we'd keep this event.
Could we add a BluetoothGattServiceEvent? We'd that way avoid re-enumerating 'services' property. We could also spilt events around CRUD: Added/Changed/Removed.

Same comment(s) for BluetoothGattCharacteristic


Julien Racle

unread,
Sep 12, 2014, 6:06:51 AM9/12/14
to mozilla-d...@lists.mozilla.org
Concerning 'value' property, I'm in sync. with Jeffery/Arman's comments.
See indeed https://github.com/WebBluetoothCG/web-bluetooth/issues/37.

Ehsan Akhgari

unread,
Sep 12, 2014, 3:00:05 PM9/12/14
to Ben Tian, Jeffrey Yasskin, Eric Chou, Shawn Huang, Jocelyn Liu, dev-webapi, Paul Theriault, Jamin Liu
On 2014-09-11, 6:24 AM, Ben Tian wrote:
> Hi Ehsan/Jeffrey,
>
>
> Please see my response below.
>
>
> ===
>
> *Q2: BluetoothDevice.connectGatt() and BluetoothDevice.gatt*
>
>>> It should check whether a BluetoothGatt object already exists before
>
>>> creating a new one. If yes it would reuse the existing one. *I'll revise
>
>>> the prose.*
>
>>
>
>> Having this method do different things depending on whether it has been
>
>> called before is really suboptimal. Please don't do that!
>
>
> I don’t get why it's suboptimal. BluetoothDevice.connectGatt()
> establishes only initial connection, so the method should be rejected if
> BluetoothDevice.gatt already exists, which means the initial connection
> has been established before. Instead apps should call
> BluetoothGatt.connect() to re-connect.

You are assuming that whoever calls connectGatt() has a way of knowing
whether it has been called before, and that assumption is false. Please
consider this scenario: let's assume you have 1.js and 2.js authored by
two different people. And the following code is executed from them in
order:

// 1.js
device.connectGatt();

// 2.js, *before* the promise returned from the above call is resolved:
console.log(device.gatt); // will be null, since the promise from 1.js
is not resolved yet.
device.connectGatt();

// the promise from 1.js is resolved.

// the promise from 2.js is rejected. There is no way for the author of
2.js to correctly check to see whether they are supposed to call
connectGatt, or use this cached gatt property.

Is the problem clear now? This is a by-product of having this weird
cached property, and having connectGatt do two different things
depending on what has happened in the application before. Please don't
do this!

> *Q4: BluetoothDevice.connectGatt() vs BluetoothGatt.connect()*
If the UA knows when it needs to do rediscovery under the hood, it can
avoid doing that when it's not needed, and just resolve the promise
returned from discover() immediately, right? If the UA doesn't know
when this needs to be done, then how can we solve this issue?

I may not know everything involved in the design here, but as things
stand here, the semantics of this part of the API is broken, as I have
demonstrated above. Please feel free to propose an alternate solution
that doesn't have the problems that I have mentioned, and still
satisfies the service discovery concerns. Thanks!

> *Q5: Is the BluetoothGattService object returned from findService
> guaranteed to be available synchronously?*
>
>
>> That wasn't really my question. My question was, is this guaranteed to be available
>
>> synchronously on all implementations, always? If not, we should make the method
>
>> asynchronous *even* if we can implement it synchronously right now.
>
>
> I see. It seems to me asynchronous methods are always preferred since
> they cover synchronous cases. Do you have examples/guidelines to
> understand when synchronous methods are more suitable than asynchronous
> ones?

Synchronous methods should *only* be used if you can reasonably
demonstrate that they will always have the answer available immediately,
which means that the answer should not depend on things stored on remote
servers, incoming data stream from external devices, talking to another
process through IPC in a multi-process UA, etc. That is of course hard,
so in most cases, you'd want asynchronous operations. But please feel
free to ask on a case by case basis whenever you have doubts and I and
others would be happy to help you figure out what things you need to pay
attention to. I tried to do that in this case, for example. :-)

> *Q7: (BluetoothGattCharacteristic) Please use WebIDL enums, not numeric
> enums.*
>
>> …
>
>> Interesting. Could you explain how you intend developers to use the WRITE_TYPE_* values? I
>
>> only seehttps://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGattCharacteristic#writeType
> using them, and it's readonly, so it seems redundant with
>
>> these 3 bits in https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGattCharacteristic#properties_2.
>
>
> Whoops, you’re right. We should make
> BluetoothGattCharacteristic.writeType set-able. I've corrected it.
>
>
> *Q8: Is there any reason for having the "value" property?*
>
>>> The apps can cache the value property themselves if they want to. These
>
>>> types of weird cache only properties don't have any prior art. In general,
>
>>> if something is as simple as this (by setting an expando property) to
>
>>> implement in the application code in JS, we should try to avoid adding it to
>
>>> the API.
>
>
> Cache in JS + "value" property removal is worth discussing. One minor
> downside is that apps would have to associate values to characteristics
> and descriptors. Let us discuss internally first since other use cases
> and APIs (e.g., BluetoothCharacteristicEvent) are also involved.

OK. I suspect you may be trying to solve problems that I cannot see
right now, in that case please feel free to bring those up! At any
rate, I really doubt these cached properties are the right solution.

> *Q9: (BluetoothGatt) Reliable Writes*
>
>> Whoops, thanks for the correction. You _might_ be able to save
>
>> developers some work by validating the result yourself and rejecting
>
>> the promise if it's incorrect, but I don't know if that would actually
>
>> wind up being better.
>
>
> It’s do-able but makes BluetoothCharacteristic.writeValue() behave
> inconsistently between normal writes and reliable writes: resolve in
> normal writes; reject in reliable writes if validation fails.

I don't see any inconsistency. Presumably you need to reject the
promise in the case of non-reliable writes as well if a runtime error
prevents the write to finish. Right?

> ------------------------------------------------------------------------
> *寄件者: *"Jeffrey Yasskin" <jyas...@google.com>
> *收件者: *"Ehsan Akhgari" <ehsan....@gmail.com>
> *副本: *"Ben Tian" <bt...@mozilla.com>, "dev-webapi"
> <dev-w...@lists.mozilla.org>, "Eric Chou" <ec...@mozilla.com>, "Shawn
> Huang" <shu...@mozilla.com>, "Jamin Liu" <ja...@mozilla.com>, "Jocelyn
> Liu" <jo...@mozilla.com>, "Paul Theriault" <pau...@mozilla.com>
> *寄件備份: *2014 9 月 11 星期四 上午 5:41:49
> *主旨: *Re: WebBluetooth BLE GATT client API

Ben Tian

unread,
Sep 15, 2014, 4:12:53 AM9/15/14
to Julien Racle, mozilla-d...@lists.mozilla.org
Hi Julien,

Thanks for your feedback. Please see my comment below.

===
2 things could be debated though:
1. the way we get BluetoothGatt (GAP connect/disconnect)
2. the way BluetoothGatt gives access to services (service discovery)

#1. 'connectGatt' gives (a singleton) BluetoothGatt instance. Fine for me. Calling disconnect() on this instance leads to GAP disconnecting it.
I also agree we could have a 'gatt' property on BluetoothDevice instances, which would be null if device is BT classic only. (We could also over-engineer a bit with a more generic access to profiles).
Then we could do bluetoothDevice.gatt.connect(), as we do for reconnection().
It adds a step but has the nice side to give more explicit behavior.

Got your point. We'll consider this way along with Ehsan's suggestions.

#2. I'm unclear about 'services' property vs 'findService' behavior. Seems that the property is filled, hence services are discovered automatically in a synchronous way. That for sure is convenient but "sub-optimal". I would also consider reflecting BLE spec more closely by:
- 1. using Promises: so that we can implement synchronously or asynchronously under the hood.
- 2. I would then change BluetoothGatt, removing 'findService'. I'll add:
Promise<sequence<BluetoothService>> getPrimaryServices() (could also be called discoverPrimaryServices()
Promise<BluetoothService> getPrimaryService(UUID)
I'd keep 'services' property, but make it lazily-initialized. It'd be updated upon gatt service changed of course, and we'd keep this event.

Can you specify "lazy-initialized" more clearly? How would 'services' property change after getPrimaryService(UUID) is resolved?

Could we add a BluetoothGattServiceEvent? We'd that way avoid re-enumerating 'services' property. We could also spilt events around CRUD: Added/Changed/Removed.

To split events around CRUD, we have to map the attribute handle range of "Service Changed" characteristic (BT spec 4.1.3.7.1) to the added/changed/removed services. However some bluetooth stacks (e.g., bluedroid) don't expose attribute handle range of all services for mapping. Therefore we decide to keep only an unified service changed event handler for compatibility with general bluetooth stacks.

Same comment(s) for BluetoothGattCharacteristic

Do you mean also split events for added/changed/removed characteristics?

===

-Ben

Ben Tian

unread,
Sep 15, 2014, 6:18:30 AM9/15/14
to Ehsan Akhgari, Eric Chou, Shawn Huang, Jocelyn Liu, dev-webapi, Paul Theriault, Jeffrey Yasskin, Jamin Liu
Hi Ehsan,

===
> *Q2: BluetoothDevice.connectGatt() and BluetoothDevice.gatt*
You are assuming that whoever calls connectGatt() has a way of knowing
whether it has been called before, and that assumption is false. Please
consider this scenario: let's assume you have 1.js and 2.js authored by
two different people. And the following code is executed from them in
order:

// 1.js
device.connectGatt();

// 2.js, *before* the promise returned from the above call is resolved:
console.log(device.gatt); // will be null, since the promise from 1.js
is not resolved yet.
device.connectGatt();

// the promise from 1.js is resolved.

// the promise from 2.js is rejected. There is no way for the author of
2.js to correctly check to see whether they are supposed to call
connectGatt, or use this cached gatt property.

Is the problem clear now? This is a by-product of having this weird
cached property, and having connectGatt do two different things
depending on what has happened in the application before. Please don't
do this!

Got your point here. Let us discuss internally to come up an alternative for this problem.

> *Q4: BluetoothDevice.connectGatt() vs BluetoothGatt.connect()*
If the UA knows when it needs to do rediscovery under the hood, it can
avoid doing that when it's not needed, and just resolve the promise
returned from discover() immediately, right? If the UA doesn't know
when this needs to be done, then how can we solve this issue?

Disagree. UA knows when to do rediscovery and do it only once under the hood before notifying all apps; But when multiple apps call discover(), UA cannot resolve subsequent discover() immediately since the 1st one may be still ongoing. Resolving subsequent calls immediately misleads apps that the discover() is done, and makes them access out-dated data.

> *Q5: Is the BluetoothGattService object returned from findService
> guaranteed to be available synchronously?*
Synchronous methods should *only* be used if you can reasonably
demonstrate that they will always have the answer available immediately,
which means that the answer should not depend on things stored on remote
servers, incoming data stream from external devices, talking to another
process through IPC in a multi-process UA, etc. That is of course hard,
so in most cases, you'd want asynchronous operations. But please feel
free to ask on a case by case basis whenever you have doubts and I and
others would be happy to help you figure out what things you need to pay
attention to. I tried to do that in this case, for example. :-)

Understood. Thanks for the clear explanation:)

> *Q8: Is there any reason for having the "value" property?*
> Cache in JS + "value" property removal is worth discussing. One minor
> downside is that apps would have to associate values to characteristics
> and descriptors. Let us discuss internally first since other use cases
> and APIs (e.g., BluetoothCharacteristicEvent) are also involved.
OK. I suspect you may be trying to solve problems that I cannot see
right now, in that case please feel free to bring those up! At any
rate, I really doubt these cached properties are the right solution.



Do you mean whether it's the right solution to 1) keep cache locally or 2) keep cache in gecko or in JS? 1) is necessary based on reasons in [1] and the BT spec also encourages caching (BT spec 4.1.3.2.5.2); 2) is the one I meant to discuss internally, although it introduces additional burden for JS apps.

[1] https://github.com/WebBluetoothCG/web-bluetooth/issues/37

> *Q9: (BluetoothGatt) Reliable Writes*
> It’s do-able but makes BluetoothCharacteristic.writeValue() behave
> inconsistently between normal writes and reliable writes: resolve in
> normal writes; reject in reliable writes if validation fails.
I don't see any inconsistency. Presumably you need to reject the
promise in the case of non-reliable writes as well if a runtime error
prevents the write to finish. Right?

Then UA should inform apps of validation error rather than runtime error for non-reliable writes. Also UA has to remember data to write in order to compare later for apps. Isn't this inconsistent to the "value" property case that lets apps cache value themselves?

===
-Ben


Ehsan Akhgari

unread,
Sep 15, 2014, 4:58:43 PM9/15/14
to Ben Tian, Eric Chou, Shawn Huang, Jocelyn Liu, dev-webapi, Paul Theriault, Jeffrey Yasskin, Jamin Liu
Sounds good!

> > *Q4: BluetoothDevice.connectGatt() vs BluetoothGatt.connect()*
> If the UA knows when it needs to do rediscovery under the hood, it can
> avoid doing that when it's not needed, and just resolve the promise
> returned from discover() immediately, right? If the UA doesn't know
> when this needs to be done, then how can we solve this issue?
>
> Disagree. UA knows when to do rediscovery and do it only once under the
> hood before notifying all apps; But when multiple apps call discover(),
> UA cannot resolve subsequent discover() immediately since the 1st one
> may be still ongoing. Resolving subsequent calls immediately misleads
> apps that the discover() is done, and makes them access out-dated data.

When I said immediately, I assumed that the discovery is finished.
Otherwise, we can resolve the promises as soon as the ongoing discovery
is finished, and not attempt to initiate a new discovery when there is
already one in flight, right?
I have nothing against caching on the implementation sde, but we don't
need to expose that concept to JS. Ideally we'd have the exact same API
that the JS code will call into, and we'd just resolve the promises
sooner if we have the information cached. i.e., the client code should
be unaware of this caching.

> > *Q9: (BluetoothGatt) Reliable Writes*
> > It’s do-able but makes BluetoothCharacteristic.writeValue() behave
> > inconsistently between normal writes and reliable writes: resolve in
> > normal writes; reject in reliable writes if validation fails.
> I don't see any inconsistency. Presumably you need to reject the
> promise in the case of non-reliable writes as well if a runtime error
> prevents the write to finish. Right?
>
> Then UA should inform apps of validation error rather than runtime error
> for non-reliable writes. Also UA has to remember data to write in order
> to compare later for apps. Isn't this inconsistent to the "value"
> property case that lets apps cache value themselves?

Perhaps there is something that I don't understand here, but please bear
with me. From the JS perspective, let's assume that I init a write
operation. Whether it's reliable or not doesn't matter, but in both
cases, there might be runtime errors that prevent the write from
finishing "successfully" (the definition of "successfully" of course
depends on whether we're dealing with a reliable write or not.) In my
JS program, I put the error handling logic in the promise rejection
handler. There, I may display an error message, retry the operation, or
fall back to something else, etc. In both the reliable and non-reliable
case, the error handling code goes in the rejection code for the
promise. And the code to handle a successful completion of the event
goes into the resolve handler for the promise.

Can you please help me understand where the inconsistency is coming from
with a design like the above?

Thanks!
Ehsan

Ben Tian

unread,
Sep 16, 2014, 5:53:29 AM9/16/14
to Ehsan Akhgari, Eric Chou, Shawn Huang, Jocelyn Liu, dev-webapi, Paul Theriault, Jeffrey Yasskin, Jamin Liu
Hi Ehsan,

> > *Q4: BluetoothDevice.connectGatt() vs BluetoothGatt.connect()*
When I said immediately, I assumed that the discovery is finished.
Otherwise, we can resolve the promises as soon as the ongoing discovery
is finished, and not attempt to initiate a new discovery when there is
already one in flight, right?



Then subsequent promises have to be pending until the 1st one finishes. I still don’t see why exposing discover() method is better than UA discovering under the hood. I agree with that BluetoothDevice.connectGatt() method has unclear semantic but don’t think exposing additional discover() method is the optimal solution for it. Anyway, we’ll also take the unclear semantic into account when we discuss internally about next API revision.

> > *Q8: Is there any reason for having the "value" property?*
I have nothing against caching on the implementation sde, but we don't
need to expose that concept to JS. Ideally we'd have the exact same API
that the JS code will call into, and we'd just resolve the promises
sooner if we have the information cached. i.e., the client code should
be unaware of this caching.



I see. Do you suggest to replace other properties with read*() methods as long as they are cached from remote device?

> > *Q9: (BluetoothGatt) Reliable Writes*
Perhaps there is something that I don't understand here, but please bear
with me. From the JS perspective, let's assume that I init a write
operation. Whether it's reliable or not doesn't matter, but in both
cases, there might be runtime errors that prevent the write from
finishing "successfully" (the definition of "successfully" of course
depends on whether we're dealing with a reliable write or not.) In my
JS program, I put the error handling logic in the promise rejection
handler. There, I may display an error message, retry the operation, or
fall back to something else, etc. In both the reliable and non-reliable
case, the error handling code goes in the rejection code for the
promise. And the code to handle a successful completion of the event
goes into the resolve handler for the promise.

Can you please help me understand where the inconsistency is coming from
with a design like the above?



The inconsistency lies in that the rejection of write operation means only “runtime error” for normal writes but “runtime error OR validation error” for reliable writes. Apps should not treat the two kinds of error as the same one since validation error implies all prior uncommitted writes have failed instead of merely a single write operation failure. Making apps validate themselves allows apps to 1) always treat rejection as “runtime error” of a single write operation and 2) be aware of validation error themselves to handle separately.

-Ben

Ehsan Akhgari

unread,
Sep 16, 2014, 11:07:33 AM9/16/14
to Ben Tian, Eric Chou, Shawn Huang, Jocelyn Liu, dev-webapi, Paul Theriault, Jeffrey Yasskin, Jamin Liu
On 2014-09-16, 5:53 AM, Ben Tian wrote:
> Hi Ehsan,
>
>> > *Q4: BluetoothDevice.connectGatt() vs BluetoothGatt.connect()*
> When I said immediately, I assumed that the discovery is finished.
> Otherwise, we can resolve the promises as soon as the ongoing discovery
> is finished, and not attempt to initiate a new discovery when there is
> already one in flight, right?
>
> Then subsequent promises have to be pending until the 1st one finishes.

Yes, and I think that's perfectly fine.

> I still don’t see why exposing discover() method is better than UA
> discovering under the hood. I agree with that
> BluetoothDevice.connectGatt() method has unclear semantic but don’t
> think exposing additional discover() method is the optimal solution for
> it. Anyway, we’ll also take the unclear semantic into account when we
> discuss internally about next API revision.

OK, feel free to come up with counter-proposals. :-)

> > > *Q8: Is there any reason for having the "value" property?*
> I have nothing against caching on the implementation sde, but we don't
> need to expose that concept to JS. Ideally we'd have the exact same API
> that the JS code will call into, and we'd just resolve the promises
> sooner if we have the information cached. i.e., the client code should
> be unaware of this caching.
>
> I see. Do you suggest to replace other properties with read*() methods
> as long as they are cached from remote device?

It depends. If these properties are guaranteed to be available when
they are accessed by content JS, then it's fine to expose them as
properties. If however we may need to for example block the caller,
fetch the properties from the BTLE device and then return it to JS, then
they should probably be asynchronous methods returning a promise, and we
should just resolve the promise right away if we have a cached value
lying around.

I hope that my logic of thinking is clear. If it's not, please ask
questions!

> > > *Q9: (BluetoothGatt) Reliable Writes*
> Perhaps there is something that I don't understand here, but please bear
> with me. From the JS perspective, let's assume that I init a write
> operation. Whether it's reliable or not doesn't matter, but in both
> cases, there might be runtime errors that prevent the write from
> finishing "successfully" (the definition of "successfully" of course
> depends on whether we're dealing with a reliable write or not.) In my
> JS program, I put the error handling logic in the promise rejection
> handler. There, I may display an error message, retry the operation, or
> fall back to something else, etc. In both the reliable and non-reliable
> case, the error handling code goes in the rejection code for the
> promise. And the code to handle a successful completion of the event
> goes into the resolve handler for the promise.
>
> Can you please help me understand where the inconsistency is coming from
> with a design like the above?
>
> The inconsistency lies in that the rejection of write operation means
> only “runtime error” for normal writes but “runtime error OR validation
> error” for reliable writes. Apps should not treat the two kinds of error
> as the same one since validation error implies all prior uncommitted
> writes have failed instead of merely a single write operation failure.
> Making apps validate themselves allows apps to 1) always treat rejection
> as “runtime error” of a single write operation and 2) be aware of
> validation error themselves to handle separately.

But why would we ever resolve a promise for uncommitted writes?
Obviously if you do that then you're introducing this kind of
inconsistency. Why can't we wait for a write to completely finish
(including any possible validation work) before we resolve a promise? I
think this is the source of the confusion here, can you please clarify
why you want to resolve the promises before the reliable writes are
validated?

Ehsan Akhgari

unread,
Sep 16, 2014, 5:02:36 PM9/16/14
to Jeffrey Yasskin, Ben Tian, Eric Chou, Shawn Huang, Jocelyn Liu, dev-webapi, Paul Theriault, Jamin Liu
On 2014-09-16, 12:24 PM, Jeffrey Yasskin wrote:
> On Tue, Sep 16, 2014 at 2:53 AM, Ben Tian <bt...@mozilla.com
> <mailto:bt...@mozilla.com>> wrote:
>
> Hi Ehsan,
> > > *Q8: Is there any reason for having the "value" property?*
> I have nothing against caching on the implementation sde, but we don't
> need to expose that concept to JS. Ideally we'd have the exact same
> API
> that the JS code will call into, and we'd just resolve the promises
> sooner if we have the information cached. i.e., the client code should
> be unaware of this caching.
>
>
> Arman's point in
> https://github.com/WebBluetoothCG/web-bluetooth/issues/37 was that the
> application may know more about the cacheability of the data than the
> browser. If the browser or OS happens to have a value cached from a
> previous call, the application may know its still useful even if the
> browser can't tell. I'm not sure why we'd use a value property for this
> instead of adding cache-control options to the readValue() call, but
> it's true that Mac and Android do it this way:
> https://developer.apple.com/library/ios/documentation/CoreBluetooth/Reference/CBCharacteristic_Class/translated_content/CBCharacteristic.html#//apple_ref/occ/instp/CBCharacteristic/value,
> http://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#getValue().
> Windows has the cache-control option:
> http://msdn.microsoft.com/en-us/library/windows/hardware/dn263758.aspx.

What does the application know that the OS doesn't in that case? One
thing that is not clear to me is how the app/OS decides when the cache
is invalidated. Can you please shed some light on that?

Clearly the prior art doesn't lean quite one way or the other here. :/

Cheers,
Ehsan

Jeffrey Yasskin

unread,
Sep 16, 2014, 12:24:09 PM9/16/14
to Ben Tian, Eric Chou, Shawn Huang, Ehsan Akhgari, Jocelyn Liu, dev-webapi, Paul Theriault, Jamin Liu
On Tue, Sep 16, 2014 at 2:53 AM, Ben Tian <bt...@mozilla.com> wrote:

> Hi Ehsan,
> > > *Q8: Is there any reason for having the "value" property?*
> I have nothing against caching on the implementation sde, but we don't
> need to expose that concept to JS. Ideally we'd have the exact same API
> that the JS code will call into, and we'd just resolve the promises
> sooner if we have the information cached. i.e., the client code should
> be unaware of this caching.
>

Arman's point in https://github.com/WebBluetoothCG/web-bluetooth/issues/37
was that the application may know more about the cacheability of the data
than the browser. If the browser or OS happens to have a value cached from
a previous call, the application may know its still useful even if the
browser can't tell. I'm not sure why we'd use a value property for this
instead of adding cache-control options to the readValue() call, but it's
true that Mac and Android do it this way:
https://developer.apple.com/library/ios/documentation/CoreBluetooth/Reference/CBCharacteristic_Class/translated_content/CBCharacteristic.html#//apple_ref/occ/instp/CBCharacteristic/value
,
http://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#getValue().
Windows has the cache-control option:
http://msdn.microsoft.com/en-us/library/windows/hardware/dn263758.aspx.

> > *Q9: (BluetoothGatt) Reliable Writes*
> Perhaps there is something that I don't understand here, but please bear
> with me. From the JS perspective, let's assume that I init a write
> operation. Whether it's reliable or not doesn't matter, but in both
> cases, there might be runtime errors that prevent the write from
> finishing "successfully" (the definition of "successfully" of course
> depends on whether we're dealing with a reliable write or not.) In my
> JS program, I put the error handling logic in the promise rejection
> handler. There, I may display an error message, retry the operation, or
> fall back to something else, etc. In both the reliable and non-reliable
> case, the error handling code goes in the rejection code for the
> promise. And the code to handle a successful completion of the event
> goes into the resolve handler for the promise.
>
> Can you please help me understand where the inconsistency is coming from
> with a design like the above?
>
> The inconsistency lies in that the rejection of write operation means only
> "runtime error" for normal writes but "runtime error OR validation error"
> for reliable writes. Apps should not treat the two kinds of error as the
> same one since validation error implies all prior uncommitted writes have
> failed instead of merely a single write operation failure. Making apps
> validate themselves allows apps to 1) always treat rejection as "runtime
> error" of a single write operation and 2) be aware of validation error
> themselves to handle separately.
>

What sorts of "runtime errors" could happen to a reliable write that
wouldn't lead to aborting the whole transaction? The most obvious error is
that the device has moved out of range and lost the connection, which would
definitely lead to restarting the write sequence. I'm not saying there
aren't any recoverable errors, but we'll need to list the possible errors
and document how developers should recover from them in order for them to
do so.

Thanks,
Jeffrey

Jeffrey Yasskin

unread,
Sep 16, 2014, 5:18:36 PM9/16/14
to Ehsan Akhgari, Eric Chou, Shawn Huang, Jocelyn Liu, dev-webapi, Ben Tian, arma...@chromium.org, Paul Theriault, Jamin Liu
On Tue, Sep 16, 2014 at 2:02 PM, Ehsan Akhgari <ehsan....@gmail.com>
wrote:

> On 2014-09-16, 12:24 PM, Jeffrey Yasskin wrote:
>
> What does the application know that the OS doesn't in that case? One
> thing that is not clear to me is how the app/OS decides when the cache is
> invalidated. Can you please shed some light on that?
>
> Clearly the prior art doesn't lean quite one way or the other here. :/
>
>
+Arman who's more enthusiastic about the cache than I am. (For him: The
rest of the thread is at
https://groups.google.com/d/topic/mozilla.dev.webapi/Z-ZXdtigktI/discussion)

The application knows the detailed semantics of the characteristic, while
the browser/OS doesn't. e.g. maybe the characteristic exposes information
about the device that simply never changes. Or maybe the application just
set up notifications for the current temperature, but it'd be good to
populate the UI eagerly if an old value is available. The browser/OS maybe
could manage the cache more intelligently for standardized characteristics,
but the project scope is broader than that.

I believe that the OS invalidates the cache when the device is
disconnected, but I haven't found any documentation about this.

HTH,
Jeffrey

Arman Uguray

unread,
Sep 16, 2014, 11:26:45 PM9/16/14
to dev-webapi
Hi all,

I glanced over the thread and there seems to be some confusion over the
attribute cache, so I'll try to shed some light:

The Generic Attribute Profile specification allows central devices (i.e.
those that act as a GATT client) to implement an attribute cache so that
attribute discovery procedures don't need to be performed multiple times
across connections (and during a single connection). This mainly saves
power by restricting the number of over-the-air requests that are made from
the client to the server. Especially on peripherals that host a lot of
services this optimization translates to power-savings, which is the whole
point of Bluetooth Low Energy.

By performing service discovery and maintaining a list of discovered
services, characteristics, and descriptors, we are in effect implementing
an attribute cache, where we're caching (either in some high-level manner
or directly) the attribute handle, type (i.e. UUID), and value of all
attributes that act as a service, characteristic, and descriptor
DEFINITION. For example, a primary service definition attribute has the
service's range start handle as its handle, the primary service UUID 0x2800
as its attribute type and the UUID of the GATT based service that is being
declared as its attribute value. Similarly, there are attributes that act
as characteristic definitions, include definitions, and so on.

Attributes that act as definitions have values that are in a very specific
format as declared by the Generic Attribute Profile. As we are defining an
API (i.e. WebBluetooth) that implements the Generic Attribute Profile, the
browser/OS has direct context on these kinds of attribute values: i.e. the
UA obtains the values of all such attributes during the discovery
procedures, which it potentially reinitiates if it receives an indication
from the "Service Changed Characteristic" of the "GATT Service" (the GATT
Service is a distinct GATT based service with UUID 0x1801). These are the
attributes that the UA has full context on and knows how they will behave
and how they will be formatted.

There are also attributes on which the UA doesn't have a full context. The
"Characteristic Value Declaration" and "Characteristic Descriptor
Declaration" attributes have values (and value permissions) that are
specified by the Generic Attribute Profile as "Higher layer profile or
implementation specific". This is a key point in this discussion, since,
while we're specifying (via WebBluetooth) how a UA can implement the
Generic Attribute Profile, it is the application using WebBluetooth that is
implementing a higher-level profile based on the Generic Attribute Profile.

Hence, the application knows everything about the specific profile that its
implementing while the UA doesn't know anything that is above the GATT
layer. This is why it's difficult for the UA to be able to determine if a
characteristic/descriptor value needs to get updated and whether or not a
cached value is sufficient for an application at a given point.

This is why there may be some value in providing the cache value to
applications, so that the app can make the decision as to whether or not
the cached characteristic value is enough or it's necessary to send a
request over the air (which will effect battery life).

Overall, I'm not particularly opinionated on this. My simple stance is,
since most native platform implementations already provide a cached
characteristic/descriptor value, why not just expose it to apps? It would
be an optional field that gets updated if a read response or a
notification/indication is received for a characteristic, which the apps
could then access.

Sorry about the novel :)

On Tue Sep 16 2014 at 2:18:57 PM Jeffrey Yasskin <jyas...@google.com>

Arman Uguray

unread,
Sep 16, 2014, 8:58:20 PM9/16/14
to Jeffrey Yasskin, Ehsan Akhgari, Eric Chou, Shawn Huang, Jocelyn Liu, dev-webapi, Ben Tian, arma...@chromium.org, Paul Theriault, Jamin Liu
-Arman

Ben Tian

unread,
Sep 17, 2014, 10:28:17 AM9/17/14
to Arman Uguray, Eric Chou, Shawn Huang, Ehsan Akhgari, Jocelyn Liu, dev-webapi, Paul Theriault, arma...@chromium.org, Jeffrey Yasskin, Jamin Liu


Thank Arman for the insight and detailed explanation!




Hi Ehsan,

The following are items we decide to revise and to keep unchanged according to our internal discussion. Please check them (especially unchanged items) and let us know further feedback. Thanks.

===
TO REVISE


Q5: Is the BluetoothGattService object returned from findService guaranteed to be available synchronously?

Ehsan: Synchronous methods should *only* be used if you can reasonably demonstrate that they will always have the answer available immediately, which means that the answer should not depend on things stored on remote servers, incoming data stream from external devices, talking to another process through IPC in a multi-process UA, etc.




We decide to remove find*() methods as JS can find the service/characteristic/descriptor with matched UUID by searching on cached array themselves.




Q7: (BluetoothGattCharacteristic) Please use WebIDL enums, not numeric enums.

Ben: Boolean dictionary seems a suitable option that we didn’t consider for PROPERTY_*, PERMISSION_*, and WRITE_TYPE_*. Let us discuss internally and bring back some conclusion.




We’ll revise PROPERTY_*, PERMISSION_*, and WRITE_TYPE_* to boolean dictionaries.




===

UNCHANGED

Q2: BluetoothDevice.connectGatt() and BluetoothDevice.gatt

Ehsan: You are assuming that whoever calls connectGatt() has a way of knowing whether it has been called before, and that assumption is false. Please consider this scenario: let's assume you have 1.js and 2.js authored by two different people. And the following code is executed from them in order:




// 1.js

device.connectGatt();




// 2.js, *before* the promise returned from the above call is resolved:

console.log(device.gatt); // will be null, since the promise from 1.js is not resolved yet.

device.connectGatt();




// the promise from 1.js is resolved.




// the promise from 2.js is rejected. There is no way for the author of 2.js to correctly check to see whether they are supposed to call

connectGatt, or use this cached gatt property.




For this problem we propose following handling to make apps aware of what happened before:

- Keep device.gatt in sync among apps

- Re-order operations in connectGatt() as following:

1) connect to remote LE device

2) discover services offered by remote LE device as well as their characteristic and descriptors

3) create BluetoothGatt and assign to device.gatt

- If a connectGatt() call comes while prior call is ongoing, keep this promise pending until the prior one is resolved/rejected and resolve/reject the later one accordingly.




As a result, device.gatt of 2.js is still null until connectGatt() of 1.js is resolved in your scenario. Rewrite it as following:




// 1.js

device.connectGatt();




// 2.js, *before* the promise returned from the above call is resolved:

console.log(device.gatt); // will be null, since the promise from 1.js is not resolved yet.

device.connectGatt(); // the promise is pending until the promise from 1.js is resolved/rejected.




// the promise from 1.js is resolved. And the promise from 2.js is also resolved. Both device.gatt of 1.js and 2.js become non-null.




// OR, the promise from 1.js is rejected. And the promise from 2.js is rejected as well. Both device.gatt of 1.js and 2.js remain null. Either 1.js or 2.js should call connectGatt() again to establish initial connection to remote LE device.








Q4: BluetoothDevice.connectGatt() vs BluetoothGatt.connect()

Ehsan: If the UA knows when it needs to do rediscovery under the hood, it can avoid doing that when it's not needed, and just resolve the promise returned from discover() immediately, right? If the UA doesn't know when this needs to be done, then how can we solve this issue?




Reply to your prior suggestion: UA does know when it needs to do rediscovery but doesn’t know whether or not itself has done the needed discovery, as apps control timings to discover instead of UA. So UA cannot res