Re: WebBluetooth BLE GATT client API

214 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
----- 原始郵件 -----

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 resolve the subsequent promises immediately since it’s unaware of which promises are “subsequent.” Consider the following example with discover() method exposed:




// 1.js calls discover() right after connect() is resolved. Service discovery is completed.

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

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

...

}

}




// 2.js only knows device.gatt.connectionState is “connected” and wants to discover services.

if (device.gatt.connectionState === “connected”) {

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

...

}

// UA has to discover services again unnecessarily since it doesn’t know whether or not service discovery for initial connection is done before.

}




The problem becomes worse as more apps connect and call discover() method 1-by-1, while only once is really required. Therefore we prefer UA to discover services implicitly in connectGatt() method and not expose discover() method to apps.







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

Agree with Arman that only apps know whether or not the cached value is sufficient. Prefer to keep “value” property.







Q9: Reliable Writes

Ehsan: But why would we ever resolve a promise for uncommitted writes? Obviously if you do that then you're introducing this kind of inconsistency.




Even with validation these writes are still uncommitted untilall values to write are correctly queued on remote server and executed. It’s the behavior of reliable write defined in BT spec. Making UA validate written value doesn’t help on the inconsistency.




Ehsan: 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?




Resolving promises after validation means the single write operation includes validation, and rejection may result from validation error besides runtime error. However we want each single write operation is resolved/rejected based on the operation itself only, so a single rejection may simply imply “retry” (no matter reliable write or not) rather than abortion of whole reliable write. On the other side, leaving validation to apps makes themselves be aware of and handle errors that fails whole reliable write transaction instead of a single write operation only.




===

-Ben


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

Arman Uguray

unread,
Sep 17, 2014, 4:17:33 PM9/17/14
to Ben Tian, Eric Chou, Shawn Huang, Ehsan Akhgari, Jocelyn Liu, dev-webapi, Paul Theriault, Jeffrey Yasskin, Jamin Liu
Hi Ben,

I'm a bit late to the discussion here but I have some questions about
reliable writes (mostly out of confusion and because I can't access the
rest of the thread for some reason).

What kind of an API do you have in mind here? What do you mean by "leaving
validation to apps"? Are you talking about an API in the form of
"prepareWrite + executeWrite" as opposed to a simple "writeValue" that
handles the details of a long write internally?

-Arman
> ------------------------------
> *寄件者: *"Arman Uguray" <arma...@google.com>
> *收件者: *"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>,
> arma...@chromium.org
> *寄件備份: *2014 9 月 17 星期三 上午 8:58:20
> *主旨: *Re: WebBluetooth BLE GATT client API

Jeffrey Yasskin

unread,
Sep 17, 2014, 11:22:52 PM9/17/14
to Ben Tian, Eric Chou, Shawn Huang, Ehsan Akhgari, Jocelyn Liu, dev-webapi, arma...@chromium.org, Paul Theriault, Jamin Liu, Arman Uguray
On Wed, Sep 17, 2014 at 7:28 AM, Ben Tian <bt...@mozilla.com> wrote:

> 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.
>

A property whose value isn't a Promise is equivalent to a synchronous
method, so this doesn't really address the concern. When working on a
polyfill for our W3C proposal, I also realized that the
chrome.bluetoothLowEnergy API exposes the services list behind an
asynchronous callback so it can retrieve the value from the browser
process. I think that increases the importance of putting these behind a
Promise.
https://developer.chrome.com/apps/bluetoothLowEnergy#method-getServices


> 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.
>

Cool. Can you explain how the permissions property works?
https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGattCharacteristic#permissions
is light on details, but my understanding is that the GATT protocol doesn't
actually express permissions to the client. The server, of course, needs to
define the permissions, but your original email said you were focusing on
the client API for now, and this property would need to be writable for an
application to set permissions anyway.

On Wed, Sep 17, 2014 at 1:17 PM, Arman Uguray <arma...@chromium.org>
wrote:
>
> What kind of an API do you have in mind here? What do you mean by "leaving
> validation to apps"? Are you talking about an API in the form of
> "prepareWrite + executeWrite" as opposed to a simple "writeValue" that
> handles the details of a long write internally?
>

Calling out a possible confusion here: I believe Ben's been talking about
the transactional reliable-write procedure defined in BT4.1, 3.G.4.9.5,
which can be used for transactions across many characteristics, but the
same sequence of ATT messages is also used for writes of "long" values to a
single characteristic. I think it's totally consistent to have applications
handle cross-characteristic transactions themselves, but also have the
browser provide a simple writeValue() that can take "long" values and
handle the whole transaction itself.

See also https://github.com/WebBluetoothCG/web-bluetooth/issues/26 for some
further discussion of writeValue vs reliable writes.

Jeffrey

Ben Tian

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

Please see my comment below.

On Wed, Sep 17, 2014 at 7:28 AM, Ben Tian < bt...@mozilla.com > wrote:





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.




A property whose value isn't a Promise is equivalent to a synchronous method, so this doesn't really address the concern. When working on a polyfill for our W3C proposal, I also realized that the chrome.bluetoothLowEnergy API exposes the services list behind an asynchronous callback so it can retrieve the value from the browser process. I think that increases the importance of putting these behind a Promise. https://developer.chrome.com/apps/bluetoothLowEnergy#method-getServices

I don't get why retrieving value from browser process increases the importance to put it behind a Promise. Are you suggesting to hide the cached services list behind a read*() method as Ehsan mentioned for "value" property?


<blockquote>





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.

</blockquote>


Cool. Can you explain how the permissions property works? https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGattCharacteristic#permissions is light on details, but my understanding is that the GATT protocol doesn't actually express permissions to the client. The server, of course, needs to define the permissions, but your original email said you were focusing on the client API for now, and this property would need to be writable for an application to set permissions anyway.

You're right. The permission property seems useless in client API since servers don't expose it. I'll remove it and put back once we are designing GATT server API. The permission property should be set when the characteristic is newly added to a service on GATT server. Thanks!

- Ben

Ben Tian

unread,
Sep 17, 2014, 10:33:20 PM9/17/14
to Arman Uguray, Eric Chou, Shawn Huang, Ehsan Akhgari, Jocelyn Liu, dev-webapi, Paul Theriault, Jeffrey Yasskin, Jamin Liu
Hi Arman,

> What kind of an API do you have in mind here? What do you mean by " leaving validation to apps"?

We are proposing [1] for reliable writes. By "leaving validation to apps" I mean UA only returns values in server responses (prepareWrite responses) to apps and let apps validate by themselves.
[1] beginReliableWrite() method description: https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#beginReliableWrite.28.29

> Are you talking about an API in the form of "prepareWrite + executeWrite" as opposed to a simple "writeValue" that handles the details of a long write internally?

Yes I'm talking about "prepareWrite + executeWrite" instead of a simple "writeValue", but only reliable writes are considered.

-Ben

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

寄件者: "Arman Uguray" <arma...@chromium.org>
收件者: "Ben Tian" <bt...@mozilla.com>
副本: "Jeffrey Yasskin" <jyas...@google.com>, "Ehsan Akhgari" <ehsan....@gmail.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 月 18 星期四 上午 4:17:33
主旨: Re: WebBluetooth BLE GATT client API

Hi Ben,

I'm a bit late to the discussion here but I have some questions about reliable writes (mostly out of confusion and because I can't access the rest of the thread for some reason).

What kind of an API do you have in mind here? What do you mean by " leaving validation to apps"? Are you talking about an API in the form of "prepareWrite + executeWrite" as opposed to a simple "writeValue" that handles the details of a long write internally?

-Arman

On Wed Sep 17 2014 at 7:28:19 AM Ben Tian < bt...@mozilla.com > wrote:





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.




Arman Uguray

unread,
Sep 18, 2014, 12:54:16 PM9/18/14
to Ben Tian, Eric Chou, Shawn Huang, Ehsan Akhgari, Jocelyn Liu, dev-webapi, Paul Theriault, Jeffrey Yasskin, Jamin Liu
Hi Ben,

> What kind of an API do you have in mind here? What do you mean by "leaving
> validation to apps"?
>
> We are proposing [1] for reliable writes. By "leaving validation to apps"
> I mean UA only returns values in server responses (prepareWrite responses)
> to apps and let apps validate by themselves.
> [1] beginReliableWrite() method description:
> https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#beginReliableWrite.28.29
>

Why leave this burden to the application? A verified write simply consists
of checking that the value+offset received in a Prepare Write Response
matches the ones that were sent in the original Prepare Write Request. The
UA can easily make this check and abort the reliable writes, whereas the
application will have to remember each value+offset that it individually
sent out.

Speaking of which, the writeValue method doesn't seem to have the offset
field. So is the purpose of the functions you linked to is to make multiple
<MTU sized writes across different characteristics?


> Are you talking about an API in the form of "prepareWrite + executeWrite"
> as opposed to a simple "writeValue" that handles the details of a long
> write internally?
>
> Yes I'm talking about "prepareWrite + executeWrite" instead of a simple
> "writeValue", but only reliable writes are considered.
>

I see. Does the abortReliableWrite correspond to sending an ATT protocol
Execute Write request with the "Cancel" flag? If so, how does the UA manage
writes initiated across different apps? E.g. what happens if multiple apps
started doing verified writes and one of them called abort before anyone
calls execute? Does that abort everyone's writes (which it will do if the
ATT request is made to the remote end)? Just trying to get a sense of how
the API behaves here.

I'm apprehensive about adding such an API to the WebBluetooth spec. In my
opinion, the application shouldn't have direct access to ATT protocol
concepts here and GATT does a good job in defining higher-level procedures
that wrap well around ATT operations. With reliable writes (and long writes
in general) you get into this situation where an app (or multiple apps)
might initiate a long/reliable write to multiple characteristics and a
failure occurs in one of the Prepare Write requests, either say an ATT
protocol error response or failure to verify. How do you recover from this?
Do you resend the failed PDU until it succeeds? Do you abort the procedure
(as the GATT spec says you should), which would then abort everything that
you have prepared? Or maybe everything goes well and one of the
characteristics gets all of its values written. But what if another
characteristic has more pending writes to prepare? If you now execute the
write, then you end up with a partially written characteristic value. What
if multiple apps are writing to the same characteristic with different
values?

In short, this can easily get messy. I think it's cleaner to have the UA
worry about how write requests should be queued, perhaps isolated to one
characteristic at a time (which is at least what I did in Chrome - though
of course it's not necessarily the right answer) and prevent applications
from carrying the burden of how these writes should be managed.

In the end apps will want to just write to a characteristic. Maybe a simple
"reliable" flag is enough to determine what kind of write to perform. There
is even a characteristic property called "Reliable Write" that tells you if
a characteristic supports reliable writes at all, so the UA could even do
some magic there.

So, these are some things to think about. I know that the Android
BluetoothGatt API looks exactly the way you have it, though it's not
necessarily the right way to do things. Anyway, just my 2 cents on the
matter :)

Cheers,
Arman

>

Jeffrey Yasskin

unread,
Sep 18, 2014, 4:14:58 PM9/18/14
to Ben Tian, Eric Chou, Shawn Huang, Ehsan Akhgari, Jocelyn Liu, dev-webapi, armansito, Paul Theriault, Jamin Liu, Arman Uguray
On Thu, Sep 18, 2014 at 3:01 AM, Ben Tian <bt...@mozilla.com> wrote:

> Hi Jeffrey,
>
> Please see my comment below.
>
> On Wed, Sep 17, 2014 at 7:28 AM, Ben Tian <bt...@mozilla.com> wrote:
>
>> 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.
>>
>
> A property whose value isn't a Promise is equivalent to a synchronous
> method, so this doesn't really address the concern. When working on a
> polyfill for our W3C proposal, I also realized that the
> chrome.bluetoothLowEnergy API exposes the services list behind an
> asynchronous callback so it can retrieve the value from the browser
> process. I think that increases the importance of putting these behind a
> Promise.
> https://developer.chrome.com/apps/bluetoothLowEnergy#method-getServices
>
> I don't get why retrieving value from browser process increases the
> importance to put it behind a Promise. Are you suggesting to hide the
> cached services list behind a read*() method as Ehsan mentioned for "value"
> property?
>

Ehsan wrote, "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 ... talking
to another process through IPC in a multi-process UA." If the cached
services, characteristics, or descriptors list lives in the Browser
process, we have to send an IPC when the renderer wants to retrieve it, and
we shouldn't block the whole renderer waiting for the IPC to return. We
could cache the entire GATT tree on each renderer that opens the device,
but the Chrome API chose not to.

Ben Tian

unread,
Sep 18, 2014, 11:33:36 PM9/18/14
to Jeffrey Yasskin, Eric Chou, Shawn Huang, Ehsan Akhgari, Jocelyn Liu, dev-webapi, armansito, Paul Theriault, Jamin Liu, Arman Uguray
Hi Jeffrey and Ehsan,

Please see my reply below.




On Wed, Sep 17, 2014 at 7:28 AM, Ben Tian < bt...@mozilla.com > wrote:

<blockquote>



TO REVISE

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



A property whose value isn't a Promise is equivalent to a synchronous method, so this doesn't really address the concern. When working on a polyfill for our W3C proposal, I also realized that the chrome.bluetoothLowEnergy API exposes the services list behind an asynchronous callback so it can retrieve the value from the browser process. I think that increases the importance of putting these behind a Promise. https://developer.chrome.com/apps/bluetoothLowEnergy#method-getServices

I don't get why retrieving value from browser process increases the importance to put it behind a Promise. Are you suggesting to hide the cached services list behind a read*() method as Ehsan mentioned for "value" property?

</blockquote>


Ehsan wrote, "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 ... talking to another process through IPC in a multi-process UA." If the cached services, characteristics, or descriptors list lives in the Browser process, we have to send an IPC when the renderer wants to retrieve it, and we shouldn't block the whole renderer waiting for the IPC to return. We could cache the entire GATT tree on each renderer that opens the device, but the Chrome API chose not to.

I see. But this further confuses me that none of the properties should exist since they all "can be implemented" to get through IPC. Even though some UA implementations already keep the latest value in each renderer/content process, applications still have to create promises to read value and handle their resolving and rejection. It may be more JS like but seems too heavy and cumbersome.

Ehsan, can you comment on my confusion above? Thanks.

-Ben

Julien Racle

unread,
Sep 19, 2014, 11:52:24 AM9/19/14
to mozilla-d...@lists.mozilla.org
Guys, sorry to reply to this long-thread with my little experience of JavaScript and brand new knowledge of BT internals. So please pardon me in advance :)..

----

Concerning debate around reliable write I'm not strongly opinionated between writeValue with option vs separate methods, and understand both arguments of you guys..

----

I'd like to add my few bucks on debate around Promises/callbacks vs properties/functions.

I group Promises and callbacks cause they allow asynchronous support. chrome.bluetooth* API makes use of callbacks, which is great, but lacks error-handling that comes with Promises. Hence I'll consider only Promises here.
(I see btw that both FirefoxOS and W3C's web-bluetooth API use them).

As you also state, properties and functions are conceptually the same, since they return immediately. Throughout FirefoxOS API, we've got debate around following properties:
- BluetoothDevice.gatt
- BluetoothGatt.services
- BluetoothGattService.characteristics / BluetoothGattService.includeServices (typo here Ben: should be BluetoothGattService.includedServices, no?)
- BluetoothGattCharacteristic.value

Part of the debate is also around caching, should it be at API level (gatt, services...), or at protocol level (BluetoothGattCharacteristic.value).

IMO, since BluetoothGattCharacteristic.value represents underlying cached value, it makes sense to keep both this property and the Promises read/writeValue.

I'm a bit uncomfortable with other properties.

- BluetoothDevice.gatt :

I understand behavior of this property, and that it can work even if multiple scripts are in the game (or IPC as well). I think we could get rid of it and keep only connectGatt() (that would be re-entrant).

Indeed, we will still have to check whether BluetoothDevice.gatt is null or not at some point, which for me is much less efficient as regards both error recovery and code spreading than handling connectGatt() being rejected.

Suppose that device is a BluetoothDeviceType.classic one, then this gatt property will be always null. Accessing it directly will be useless, but calling connectGatt() will be rejected with according error.

- BluetoothGatt.services / BluetoothGattService.characteristics / BluetoothGattService.includeServices :

For above props, I see the advantage of using an API-level cache and pre-fetching them, but would rather favor moving to promises instead :
i.e. BluetoothGatt.getPrimaryServices(), BluetoothGatt.getPrimaryService(UUID), BluetoothGattService.getCharacteristics(), BluetoothGattService.getCharacteristic(UUID).

As a first remark, I don't see a quick way to access service and characteristics by UUID. With current API, we have to enumerate sequences am I right?

Then, using promises would also enable more flexible error-handling.
Imagine device gets out-of-range while you call BluetoothGattService.characteristics vs BluetoothGatt.getCharacteristics(). The latter will fail whereas the former won't.

What's more, you say that 'It may be more JS like but seems too heavy and cumbersome.' : IMO as soon as you've got possibility to treat errors in an callback, you've got not just sth that sounds like JS but also a consistent way of handling errors. It is also up to you to treat them, since error callback is optional; that's what makes the power of promises/tasks or whatever we call them. Look at what is done currently in winJS API for instance, most of the functions return tasks that can be chained.

Lastly, Jefferey's remark on renderers and blocking behavior is the one that makes me go in favor of using Promises.

Again, pardon the latency in my answers and my language.
You're doing great debates guys ;)

Ben Tian

unread,
Sep 22, 2014, 5:33:51 AM9/22/14
to Julien Racle, mozilla-d...@lists.mozilla.org
Hi Julien,

Please see my reply below.

--
I'd like to add my few bucks on debate around Promises/callbacks vs properties/functions.

As you also state, properties and functions are conceptually the same, since they return immediately. Throughout FirefoxOS API, we've got debate around following properties:
- BluetoothDevice.gatt
- BluetoothGatt.services
- BluetoothGattService.characteristics / BluetoothGattService.includeServices (typo here Ben: should be BluetoothGattService.includedServices, no?)

You're right. I've corrected it. Thanks for pointing out.

I'm a bit uncomfortable with other properties.

- BluetoothDevice.gatt :

I understand behavior of this property, and that it can work even if multiple scripts are in the game (or IPC as well). I think we could get rid of it and keep only connectGatt() (that would be re-entrant).

Indeed, we will still have to check whether BluetoothDevice.gatt is null or not at some point, which for me is much less efficient as regards both error recovery and code spreading than handling connectGatt() being rejected.

We keep BluetoothDevice.gatt as a pointer from BluetoothDevice to its corresponding BluetoothGatt object. Isn't there need to do so?

Suppose that device is a BluetoothDeviceType.classic one, then this gatt property will be always null. Accessing it directly will be useless, but calling connectGatt() will be rejected with according error.

- BluetoothGatt.services / BluetoothGattService.characteristics / BluetoothGattService.includeServices :

For above props, I see the advantage of using an API-level cache and pre-fetching them, but would rather favor moving to promises instead :
i.e. BluetoothGatt.getPrimaryServices(), BluetoothGatt.getPrimaryService(UUID), BluetoothGattService.getCharacteristics(), BluetoothGattService.getCharacteristic(UUID).

As a first remark, I don't see a quick way to access service and characteristics by UUID. With current API, we have to enumerate sequences am I right?

Yes, we had them but removed since we think promises are cumbersome if applications can enumerate cached arrays by their own.

Then, using promises would also enable more flexible error-handling.
Imagine device gets out-of-range while you call BluetoothGattService.characteristics vs BluetoothGatt.getCharacteristics(). The latter will fail whereas the former won't.

What's more, you say that 'It may be more JS like but seems too heavy and cumbersome.' : IMO as soon as you've got possibility to treat errors in an callback, you've got not just sth that sounds like JS but also a consistent way of handling errors. It is also up to you to treat them, since error callback is optional; that's what makes the power of promises/tasks or whatever we call them. Look at what is done currently in winJS API for instance, most of the functions return tasks that can be chained.

Lastly, Jeffrey's remark on renderers and blocking behavior is the one that makes me go in favor of using Promises.

Do you suggest other properties also become get*() methods or only the three you mentioned above? Got your and Jeffrey's point but at what condition properties should exist still confuses me.

Again, pardon the latency in my answers and my language.
You're doing great debates guys ;)

Feedbacks are always welcome:)

-Ben

Julien Racle

unread,
Sep 24, 2014, 3:17:58 AM9/24/14
to mozilla-d...@lists.mozilla.org
Hi Ben,

thanks for your reply,

see my comments below.

On Monday, September 22, 2014 11:33:51 AM UTC+2, Ben Tian wrote:
> Hi Julien,
>
>
>
> Please see my reply below.
>
>
>
> --
>
> I'd like to add my few bucks on debate around Promises/callbacks vs properties/functions.
>
>
>
> As you also state, properties and functions are conceptually the same, since they return immediately. Throughout FirefoxOS API, we've got debate around following properties:
>
> - BluetoothDevice.gatt
>
> - BluetoothGatt.services
>
> - BluetoothGattService.characteristics / BluetoothGattService.includeServices (typo here Ben: should be BluetoothGattService.includedServices, no?)
>
>
>
> You're right. I've corrected it. Thanks for pointing out.
>
>

You're welcome!

>
> I'm a bit uncomfortable with other properties.
>
>
>
> - BluetoothDevice.gatt :
>
>
>
> I understand behavior of this property, and that it can work even if multiple scripts are in the game (or IPC as well). I think we could get rid of it and keep only connectGatt() (that would be re-entrant).
>
>
>
> Indeed, we will still have to check whether BluetoothDevice.gatt is null or not at some point, which for me is much less efficient as regards both error recovery and code spreading than handling connectGatt() being rejected.
>
>
>
> We keep BluetoothDevice.gatt as a pointer from BluetoothDevice to its corresponding BluetoothGatt object. Isn't there need to do so?
>
>

BluetoothDevice.gatt behaves and is implemented correctly.

The only questionnable thing is whether or not it should be exposed to the end-user. See my comments below.

>
> Suppose that device is a BluetoothDeviceType.classic one, then this gatt property will be always null. Accessing it directly will be useless, but calling connectGatt() will be rejected with according error.
>
>

For me this property should be kept internal, and one should get it through connectGatt() only ; see Android way of doing things.

>
> - BluetoothGatt.services / BluetoothGattService.characteristics / BluetoothGattService.includeServices :
>
>
>
> For above props, I see the advantage of using an API-level cache and pre-fetching them, but would rather favor moving to promises instead :
>
> i.e. BluetoothGatt.getPrimaryServices(), BluetoothGatt.getPrimaryService(UUID), BluetoothGattService.getCharacteristics(), BluetoothGattService.getCharacteristic(UUID).
>
>
>
> As a first remark, I don't see a quick way to access service and characteristics by UUID. With current API, we have to enumerate sequences am I right?
>
>
>
> Yes, we had them but removed since we think promises are cumbersome if applications can enumerate cached arrays by their own.
>
>

Ok got it. That's a design decision then, to be compared to promises alone or promises + exposing cached arrays. See below also for a more global reflection.

>
> Then, using promises would also enable more flexible error-handling.
>
> Imagine device gets out-of-range while you call BluetoothGattService.characteristics vs BluetoothGatt.getCharacteristics(). The latter will fail whereas the former won't.
>
>
>
> What's more, you say that 'It may be more JS like but seems too heavy and cumbersome.' : IMO as soon as you've got possibility to treat errors in an callback, you've got not just sth that sounds like JS but also a consistent way of handling errors. It is also up to you to treat them, since error callback is optional; that's what makes the power of promises/tasks or whatever we call them. Look at what is done currently in winJS API for instance, most of the functions return tasks that can be chained.
>
>
>
> Lastly, Jeffrey's remark on renderers and blocking behavior is the one that makes me go in favor of using Promises.
>
>
>
> Do you suggest other properties also become get*() methods or only the three you mentioned above? Got your and Jeffrey's point but at what condition properties should exist still confuses me.
>
>

I'm also asking myself what should be (if any) the overall strategy to take :), but will share my thoughts below.

>
> Again, pardon the latency in my answers and my language.
>
> You're doing great debates guys ;)
>
>
>
> Feedbacks are always welcome:)
>
>

Great! Thanks for being so open to feedback, very much appreciated!

>
> -Ben

So going back to the debate, we will have to balance usage of :

1. properties and functions
2. events (forgot to talk about them)
3. promises (let's exclude callbacks of the debate)

Above 3 points can be compared around a couple of dimensions.
We can surely find a lot more, but here are the first that come to my mind:

a. The way they serve data (or respectively the way we retrieve data from them)
a.1 via a notification mechanism
a.2 synchronously / asynchronously
b. Performance considerations.
c. Handling / Recovering from errors.

a.1 Notification mechanism

For me is a point relatively orthogonal to the rest of the debate, but which brings to my mind interesting points. We for sure need to have a notification mechanism for being notified of characteristic value and service changes. Apart from custom implementations / APIs, the only 2 (non-debug oriented) mechanisms I know for implementing observer pattern are DOM events and Object.observe. We can consider people using the latter on properties (say BluetoothGattCharacteristic.value) to observe changes. As regards the former, as Jefferey mentionned in issue 20 (https://github.com/WebBluetoothCG/web-bluetooth/issues/20) event propagation design is to be handled with care (especially I guess considering garbage collection). My hint would be here to pass instanceId instead of entities, so that we can still garbage-collect the entities while notifying. We would then have to provide repository access methods to retrieve entities by instanceId. So I'd keep using DOM events + would ask your advice on mechanisms like Object.observe(), for which I don't have much info.

a.2 asynchronism vs synchronism

In that respect, Promises are enablers for asynchronous calls (though they can be called synchronously), and hence enable re-posting to GUI thread while avoiding hangs in caller. On the other hand, properties will have to be polled as a consequence of a promise being resolved or a change notification. In former case, you'll notice they can be also passed as a parameter to the promise (see BluetoothDevice.gatt property), and hence are kind-of duplicated for convenience. Same remark for latter case, we can pass the property in event handler along with entity ID (see a.1).

In case properties are evaluated prior to client call (e.g. enumerating services), there is no reason to expose a Promise which would add (as you state) un-necessary burden.

c. Performance considerations can be split in terms of :

- minimizing hangs : using asynchronous code.

- optimizing radio : in that category, I would consider with care usage of caches. For me in our design, we should consider keeping cache usage for gatt characteristic value as well as for sequences of services, characteristics and descriptors. To my mind, the cached must be filled on-demand so that we avoid un-necessary radio transfer (low-energy).

As regards characteristic value, we can keep the property on BluetoothGattCharacteristic object and update it when readValue promise is resolved; another option is to add an option to readValue so that it reads cached data by default (filling it for the first call), and has an option to invalidate cache. I guess this is what windows 8 API does.

We should also avoid pre-fecthing services and characteristics when they are not requested, especially when considering most apps will be interested in a subset of them (or vendor-specific services). In that respect I would not favor enumerating services automatically.

- code simplification : not exposing promises or duplicate (property/promises) if not needed.

Sorry again for the long answer, I don't want to re-open points that were already discussed.

Cheers,
Julien

Ben Tian

unread,
Sep 25, 2014, 2:09:34 PM9/25/14
to Arman Uguray, Eric Chou, Shawn Huang, Ehsan Akhgari, Jocelyn Liu, dev-webapi, Paul Theriault, Jeffrey Yasskin, Jamin Liu
Hi Arman,

Sorry for my late reply. W e agree that UA should handle long writes but "prepareWrite+executeWrite" API is necessary to specify start and end of reliables writes cross multiple characteristics. Please see comment below for details.





We are proposing [1] for reliable writes. By "leaving validation to apps" I mean UA only returns values in server responses (prepareWrite responses) to apps and let apps validate by themselves.
[1] beginReliableWrite() method description: https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/BluetoothGatt#beginReliableWrite.28.29




Why leave this burden to the application? A verified write simply consists of checking that the value+offset received in a Prepare Write Response matches the ones that were sent in the original Prepare Write Request. The UA can easily make this check and abort the reliable writes, whereas the application will have to remember each value+offset that it individually sent out.

Speaking of which, the writeValue method doesn't seem to have the offset field. So is the purpose of the functions you linked to is to make multiple <MTU sized writes across different characteristics?

You are mixing up long writes and reliable writes. We agree with you that UA should handle long write within a simple writeValue() method since long write is of single characteristic only. However reliable writes consist of multiple characteristics and "reliable" flag of a characteristic doesn't suffice to specify start & end of transactions cross multiple characteristics.

Back to your questions: again we still want applications be aware of validation error and abort reliable writes by themselves. In this way we keep writeValue() purely handle the "writing part" only excluding validation part, even though it's easy for UA to do so.


<blockquote>


Yes I'm talking about "prepareWrite + executeWrite" instead of a simple "writeValue", but only reliable writes are considered.

</blockquote>


I see. Does the abortReliableWrite correspond to sending an ATT protocol Execute Write request with the "Cancel" flag? If so, how does the UA manage writes initiated across different apps? E.g. what happens if multiple apps started doing verified writes and one of them called abort before anyone calls execute? Does that abort everyone's writes (which it will do if the ATT request is made to the remote end)? Just trying to get a sense of how the API behaves here.

These problems still exist even UA handles the write, no? The BT spec doesn't specify a way to abort only prepare write requests of a single application.

I'm apprehensive about adding such an API to the WebBluetooth spec. In my opinion, the application shouldn't have direct access to ATT protocol concepts here and GATT does a good job in defining higher-level procedures that wrap well around ATT operations. With reliable writes (and long writes in general) you get into this situation where an app (or multiple apps) might initiate a long/reliable write to multiple characteristics and a failure occurs in one of the Prepare Write requests, either say an ATT protocol error response or failure to verify. How do you recover from this? Do you resend the failed PDU until it succeeds? Do you abort the procedure (as the GATT spec says you should), which would then abort everything that you have prepared? Or maybe everything goes well and one of the characteristics gets all of its values written. But what if another characteristic has more pending writes to prepare? If you now execute the write, then you end up with a partially written characteristic value. What if multiple apps are writing to the same characteristic with different values?

In short, this can easily get messy. I think it's cleaner to have the UA worry about how write requests should be queued, perhaps isolated to one characteristic at a time (which is at least what I did in Chrome - though of course it's not necessarily the right answer) and prevent applications from carrying the burden of how these writes should be managed.

In the end apps will want to just write to a characteristic. Maybe a simple "reliable" flag is enough to determine what kind of write to perform. There is even a characteristic property called "Reliable Write" that tells you if a characteristic supports reliable writes at all, so the UA could even do some magic there.

I agree with these problems you mention but I see no help by letting UA handle them. One characteristic at a time still meets these problems on reliable writes of multiple characteristics.

Regards,
-Ben

Reply all
Reply to author
Forward
0 new messages