Hi there,
TL;DR: I have observed that BlueZ does some operations which incur in
extra latency when connecting to a bonded peripheral. I would like to
know if it's possible to remove those operations through configuration
or, if maintainers are open for it, provide some patches to remove
them.
I have a BLE peripheral with a button. The button state (key pressed /
key released) is associated to a custom characteristic (i.e. it's not
HID). The state is conveyed through ATT notifications to a central
running BlueZ (5.47, using dbus gatt api) with which it=E2=80=99s bonded.
In order to save battery, the peripheral is normally disconnected and
only starts advertising when the key is pressed. The central is always
attempting to connect. Once the connection is established, the
key-press in conveyed to the central as a notification (the central
calls StartNotify() as soon as a connection is established).
For the application=E2=80=99s purpose, it=E2=80=99s critical that the notif=
ication is
recieved with the minimum latency.
However, between the connection establishment and the notification, I
see that BlueZ does some operations which incur in extra latency.
To guide the conversation, here is a btmon dump showing what happens
in the central right after a connection is established with the
peripheral: https://pastebin.com/tQMgwK7v
My goal is to minimize the time between #6 and #18
At #7 there is an MTU exchange request, which, for this particular
peripheral is not really necessary since I know it only supports 23
bytes. Is there a way to disable that? After reading the gatt client
initialization code, it seems that no exchange is attempted if the MTU
of the central is also 23 bytes, but I don=E2=80=99t know if the default MT=
U
is configurable. Is it?
At #9 and #12 there are reads for the device name and device
appearance. I have managed to disable those by lanching bluetoothd
with --noplugin=3Ddeviceinfo,gap . So, it's not an issue anymore.
At #14 there is an ATT write to the CCC descriptor of the
button-status attribute, in order to enable notifications (maybe as a
consequence of invoking StartNotify() in the central). But, why is
this needed for a bonded peripheral? If I interpret the standard
properly, CCC descriptor values should be cached and restored across
connections among bonded devices (section "3.3.3.3 Client
Characteristic Configuration" "The Client Characteristic Configuration
descriptor value shall be persistent across connections for bonded
devices."). I, however, can't find a ccc file in the peripheral cache,
as described here
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/settings-storag=
e.txt?h=3D5.47#n46
.
At #17 the central starts discovering the primary services. Why is
this necessary? Shouldn=E2=80=99t they be simply recovered from the cache?
(According to the standard "For clients that have a trusted
relationship (i.e. bond) with the server, the attribute cache is valid
across connections."). I have verified that the cache already
contained all the primary services. However, after looking at the code
(shared/gatt-client.c ) it seems primary service discovery
(bt_gatt_discover_all_primary_services()) is always invoked :S
Finally, at #18, I get the notification from the peripheral.
I am happy to try to provide some patches if we agree that at least
some of these operations are not necessary and there is no way to
disable them through configuration.
Thanks in advance,
Alfonso Acosta
Hi Luiz and Jamie,
>> The only reason is: very high expectations of battery life. I recall
>> we played with the slave latency in the past, but we will reevaluate
>> and see if it's a feasible option. Thanks for the suggestion.
I found another reason why the slave latency doesn't fully solve the
problem. The peripheral is not advertising all the time (only on a
key-press to save battery). Thus, a large slave latency doesn't help
when (re-)connecting to the central (e.g. in a cold boot or reboot)
point at which we also expect the first notification latency to be
low.
In fact, unless I can get to deliver the notification before BlueZ
does its post-connection operations (MTU exchange, CCCD write etc ...)
, a large slave latency will probably make things worse. Which leads
me to the question below ...
> We do
> however only reload the device attributes once connecting, to avoid
> heavy io at startup, so if you are restarting bluetoothd it won't
> restore the objects right away.
This reflects what I see.
Then isn't StartNotify() racy when connecting to bonded peripherals
for the first time after bluetoothd starts?
More specifically, won't the D-Bus API miss notifications when:
1. We have a characteristic in a bonded peripheral for which
notifications when enabled in the CCCD in a prior connection to the
central (BlueZ)
2. Notifications are sent form the peripheral after the connection is
established (without waiting for a CCCD write, which should be
acceptable if the peripheral is bonded)
2. It's the first connection to the peripheral since bluetoothd started
Hi Alfonso,
On Mon, Jan 8, 2018 at 7:32 AM, Alfonso Acosta <[email protected]> w=
rote:
> Hi Jamie,
>
>>>For the application=E2=80=99s purpose, it=E2=80=99s critical that the no=
tification is recieved with the minimum latency.
>>
>> Is there any reason why you wish to have the button in a disconnected st=
ate when inactive over having it in a connected state but with a high conne=
ction interval and a large slave latency? A high slave latency means it can=
stay in sleep mode if there is nothing to report back to the central devic=
e for a specified number of packets so is perfect for battery powered devic=
es.
>
> The only reason is: very high expectations of battery life. I recall
> we played with the slave latency in the past, but we will reevaluate
> and see if it's a feasible option. Thanks for the suggestion.
>
> Regardless, I am still curious to know if disconnecting and sending
> the notification right away (as Luiz suggests) isn't racy with respect
> to StartNotify().
The reason we maintain the attributes, if the device is not temporary,
is exactly to avoid this race, but perhaps you a talking about
temporary devices? We consider temporary devices those that have not
been connected/paired or in case the devices use a random address that
has not been resolved.
Note that has been a lot of debate regarding this behavior of BlueZ,
most stacks would remove any cache for devices which have not been
paired but in BlueZ we choose to keep the cache to prevent this sort
of race, which may be caused by intermittent connections. We do
however only reload the device attributes once connecting, to avoid
heavy io at startup, so if you are restarting bluetoothd it won't
restore the objects right away.
--=20
Luiz Augusto von Dentz
Hi Jamie,
>>For the application=E2=80=99s purpose, it=E2=80=99s critical that the not=
ification is recieved with the minimum latency.
>
> Is there any reason why you wish to have the button in a disconnected sta=
te when inactive over having it in a connected state but with a high connec=
tion interval and a large slave latency? A high slave latency means it can =
stay in sleep mode if there is nothing to report back to the central device=
for a specified number of packets so is perfect for battery powered device=
s.
The only reason is: very high expectations of battery life. I recall
we played with the slave latency in the past, but we will reevaluate
and see if it's a feasible option. Thanks for the suggestion.
Regardless, I am still curious to know if disconnecting and sending
the notification right away (as Luiz suggests) isn't racy with respect
to StartNotify().
SGksDQoNCi0tLS0tT3JpZ2luYWwgTWVzc2FnZS0tLS0tDQo+SGkgdGhlcmUsDQo+DQo+VEw7RFI6
IEkgaGF2ZSBvYnNlcnZlZCB0aGF0IEJsdWVaIGRvZXMgc29tZSBvcGVyYXRpb25zIHdoaWNoIGlu
Y3VyIGluIGV4dHJhIGxhdGVuY3kgd2hlbiBjb25uZWN0aW5nIHRvIGEgYm9uZGVkIHBlcmlwaGVy
YWwuIEkgd291bGQgbGlrZSB0byBrbm93IGlmIGl0J3MgcG9zc2libGUgdG8gcmVtb3ZlIHRob3Nl
IG9wZXJhdGlvbnMgdGhyb3VnaCBjb25maWd1cmF0aW9uIG9yLCBpZiBtYWludGFpbmVycyBhcmUg
b3BlbiBmb3IgaXQsIHByb3ZpZGUgc29tZSBwYXRjaGVzIHRvIHJlbW92ZSB0aGVtLg0KPg0KPkkg
aGF2ZSBhIEJMRSBwZXJpcGhlcmFsIHdpdGggYSBidXR0b24uIFRoZSBidXR0b24gc3RhdGUgKGtl
eSBwcmVzc2VkIC8ga2V5IHJlbGVhc2VkKSBpcyBhc3NvY2lhdGVkIHRvIGEgY3VzdG9tIGNoYXJh
Y3RlcmlzdGljIChpLmUuIGl0J3Mgbm90IEhJRCkuIFRoZSBzdGF0ZSBpcyBjb252ZXllZCB0aHJv
dWdoIEFUVCBub3RpZmljYXRpb25zIHRvIGEgY2VudHJhbCBydW5uaW5nIEJsdWVaICg1LjQ3LCB1
c2luZyBkYnVzIGdhdHQgYXBpKSB3aXRoIHdoaWNoIGl04oCZcyBib25kZWQuDQo+DQo+SW4gb3Jk
ZXIgdG8gc2F2ZSBiYXR0ZXJ5LCB0aGUgcGVyaXBoZXJhbCBpcyBub3JtYWxseSBkaXNjb25uZWN0
ZWQgYW5kIG9ubHkgc3RhcnRzIGFkdmVydGlzaW5nIHdoZW4gdGhlIGtleSBpcyBwcmVzc2VkLiBU
aGUgY2VudHJhbCBpcyBhbHdheXMgYXR0ZW1wdGluZyB0byBjb25uZWN0LiBPbmNlIHRoZSBjb25u
ZWN0aW9uIGlzIGVzdGFibGlzaGVkLCB0aGUga2V5LXByZXNzIGluIGNvbnZleWVkIHRvIHRoZSBj
ZW50cmFsIGFzIGEgbm90aWZpY2F0aW9uICh0aGUgY2VudHJhbCBjYWxscyBTdGFydE5vdGlmeSgp
IGFzIHNvb24gYXMgYSBjb25uZWN0aW9uIGlzIGVzdGFibGlzaGVkKS4NCj4NCj5Gb3IgdGhlIGFw
cGxpY2F0aW9u4oCZcyBwdXJwb3NlLCBpdOKAmXMgY3JpdGljYWwgdGhhdCB0aGUgbm90aWZpY2F0
aW9uIGlzIHJlY2lldmVkIHdpdGggdGhlIG1pbmltdW0gbGF0ZW5jeS4NCg0KSXMgdGhlcmUgYW55
IHJlYXNvbiB3aHkgeW91IHdpc2ggdG8gaGF2ZSB0aGUgYnV0dG9uIGluIGEgZGlzY29ubmVjdGVk
IHN0YXRlIHdoZW4gaW5hY3RpdmUgb3ZlciBoYXZpbmcgaXQgaW4gYSBjb25uZWN0ZWQgc3RhdGUg
YnV0IHdpdGggYSBoaWdoIGNvbm5lY3Rpb24gaW50ZXJ2YWwgYW5kIGEgbGFyZ2Ugc2xhdmUgbGF0
ZW5jeT8gQSBoaWdoIHNsYXZlIGxhdGVuY3kgbWVhbnMgaXQgY2FuIHN0YXkgaW4gc2xlZXAgbW9k
ZSBpZiB0aGVyZSBpcyBub3RoaW5nIHRvIHJlcG9ydCBiYWNrIHRvIHRoZSBjZW50cmFsIGRldmlj
ZSBmb3IgYSBzcGVjaWZpZWQgbnVtYmVyIG9mIHBhY2tldHMgc28gaXMgcGVyZmVjdCBmb3IgYmF0
dGVyeSBwb3dlcmVkIGRldmljZXMuIFVzaW5nIHRoaXMgbWV0aG9kIG1lYW5zIHlvdSBkb24ndCBo
YXZlIHRoZSBzbG93ZG93biBpbiBlc3RhYmxpc2hpbmcgYSBjb25uZWN0aW9uIG9yIGV4Y2hhbmdp
bmcgR0FUVCB0YWJsZSBpbmZvcm1hdGlvbiBiZWNhdXNlIHRoZSBjb25uZWN0aW9uIGlzIGxpdmUs
IGFuZCBJIHRoaW5rIGlzIHByZWZlcmFibGUgdG8gYnVyZGVuaW5nIHRoZSBCbHVlWiBtYWludGFp
bmVycyB3aXRoIG1haW50YWluaW5nIGEgYmxhY2tsaXN0IHdoaWNoLCBpbiBteSBtaW5kLCByZWFs
bHkgc2hvdWxkbid0IGJlIGEgcGFydCBvZiBCbHVlWi4NCg0KVGhhbmtzLA0KSmFtaWUNCg==
Hi Luiz,
Thanks a lot for the detailed answer.
>> At #7 there is an MTU exchange request, which, for this particular
>> peripheral is not really necessary since I know it only supports 23
>> bytes. Is there a way to disable that? After reading the gatt client
>> initialization code, it seems that no exchange is attempted if the MTU
>> of the central is also 23 bytes, but I don=E2=80=99t know if the default=
MTU
>> is configurable. Is it?
>
> In theory the stacks does not have to hold on to the same MTU on every
> connection, this is important especially for devices where services
> can be registered dynamically
Yes, I understand that. That's why I was asking whether there was a
way to configure the default MTU of the central. Setting it to 23
should prevent the exchange from happening.
This is admittedly a workaround. The configuration should probably be
per device/connection, and I think would be useful for use-cases other
than mine. BlueZ tries to use the maximum ATT MTU (517), but that may
not be desirable in high-noise environments in which you want to have
small MTUs to mitigate the effect of retransmissions.
> so what you are asking probably require
> a blacklist of devices that don't require MTU negotiation which is
> something hard to maintain.
Not necessarily. We could add the MTU to the device info file (much
like it's done for the connection parameters
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/settings-storag=
e.txt#n287).
>> Finally, at #18, I get the notification from the peripheral.
>
> And here is the real explanation why we do all this, GATT/ATT
> notification do no share the same queue as request/response, so all of
> this shall not stop your peripheral to send the notification upfront
> regardless if there are any requests outstanding, so perhaps the
> problem is really the peripheral which expects the central to be quiet
> while it notifies?
Really good point! I will double check the device's firmware, which
fortunately I have full control of. It may even be that the device
doesn't either respect CCC persistence (which validates why BlueZ
doesn't trust the persistence-handling of devices :) ).
Let's assume I get the peripheral to send the first notification right
after the encryption is established. Even if I call StartNotify()
right after the characteristic object is created, won't I risk missing
the notification if StartNotify() isn't called and served before the
notification is received?
>From what I have observed, the D-Bus GattCharacteristic objects are
destroyed regularly (at least across bluetoothd restarts), they only
seem to be re-created when doing service-discovery and the Notifying
property is not persisted across creations.
>> I am happy to try to provide some patches if we agree that at least
>> some of these operations are not necessary and there is no way to
>> disable them through configuration.
>
> Except if you want to maintain database of devices with various
> invalid behaviors I don't think we can do much better, especially
> because GATT/ATT on the OSes like iOS and Android do come with drivers
> in a form of applications which does things in their own way we have
> to deal with these nuances smartly or end up reproducing the same
> procedures each and every app does which imo is not maintainable.
I agree that creating a blacklist database is probably unfeasible at
this point, at least without breaking unintentionally breaking
compatibility with faulty devices which slip through the cracks (which
I understand we want to avoid).
However, we could use add another option to the info file, say
"RespectsBondingPersistence" which indicates whether attributes and
CCC values are properly persisted across connection with bonded
devices.
Hi Alfonso,
On Fri, Jan 5, 2018 at 5:41 PM, Alfonso Acosta <[email protected]> w=
rote:
> Hi there,
>
> TL;DR: I have observed that BlueZ does some operations which incur in
> extra latency when connecting to a bonded peripheral. I would like to
> know if it's possible to remove those operations through configuration
> or, if maintainers are open for it, provide some patches to remove
> them.
>
> I have a BLE peripheral with a button. The button state (key pressed /
> key released) is associated to a custom characteristic (i.e. it's not
> HID). The state is conveyed through ATT notifications to a central
> running BlueZ (5.47, using dbus gatt api) with which it=E2=80=99s bonded.
>
> In order to save battery, the peripheral is normally disconnected and
> only starts advertising when the key is pressed. The central is always
> attempting to connect. Once the connection is established, the
> key-press in conveyed to the central as a notification (the central
> calls StartNotify() as soon as a connection is established).
>
> For the application=E2=80=99s purpose, it=E2=80=99s critical that the not=
ification is
> recieved with the minimum latency.
>
> However, between the connection establishment and the notification, I
> see that BlueZ does some operations which incur in extra latency.
>
> To guide the conversation, here is a btmon dump showing what happens
> in the central right after a connection is established with the
> peripheral: https://pastebin.com/tQMgwK7v
>
> My goal is to minimize the time between #6 and #18
>
> At #7 there is an MTU exchange request, which, for this particular
> peripheral is not really necessary since I know it only supports 23
> bytes. Is there a way to disable that? After reading the gatt client
> initialization code, it seems that no exchange is attempted if the MTU
> of the central is also 23 bytes, but I don=E2=80=99t know if the default =
MTU
> is configurable. Is it?
In theory the stacks does not have to hold on to the same MTU on every
connection, this is important especially for devices where services
can be registered dynamically, so what you are asking probably require
a blacklist of devices that don't require MTU negotiation which is
something hard to maintain.
> At #9 and #12 there are reads for the device name and device
> appearance. I have managed to disable those by lanching bluetoothd
> with --noplugin=3Ddeviceinfo,gap . So, it's not an issue anymore.
Again the stacks can change the name and appearance depending on the
services, therefore we read this on every connection.
> At #14 there is an ATT write to the CCC descriptor of the
> button-status attribute, in order to enable notifications (maybe as a
> consequence of invoking StartNotify() in the central). But, why is
> this needed for a bonded peripheral? If I interpret the standard
> properly, CCC descriptor values should be cached and restored across
> connections among bonded devices (section "3.3.3.3 Client
> Characteristic Configuration" "The Client Characteristic Configuration
> descriptor value shall be persistent across connections for bonded
> devices."). I, however, can't find a ccc file in the peripheral cache,
> as described here
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/settings-stor=
age.txt?h=3D5.47#n46
> .
The reasons for reprograming the CCC are normally related to faulty
devices which did not maintain state of CCC, and in some case when
they had been flashed.
> At #17 the central starts discovering the primary services. Why is
> this necessary? Shouldn=E2=80=99t they be simply recovered from the cache=
?
> (According to the standard "For clients that have a trusted
> relationship (i.e. bond) with the server, the attribute cache is valid
> across connections."). I have verified that the cache already
> contained all the primary services. However, after looking at the code
> (shared/gatt-client.c ) it seems primary service discovery
> (bt_gatt_discover_all_primary_services()) is always invoked :S
BlueZ does cache validation of the services, again this is due to
faulty devices which does not indicate changes on service changed as
they may have been flashed or something like that.
> Finally, at #18, I get the notification from the peripheral.
And here is the real explanation why we do all this, GATT/ATT
notification do no share the same queue as request/response, so all of
this shall not stop your peripheral to send the notification upfront
regardless if there are any requests outstanding, so perhaps the
problem is really the peripheral which expects the central to be quiet
while it notifies?
> I am happy to try to provide some patches if we agree that at least
> some of these operations are not necessary and there is no way to
> disable them through configuration.
Except if you want to maintain database of devices with various
invalid behaviors I don't think we can do much better, especially
because GATT/ATT on the OSes like iOS and Android do come with drivers
in a form of applications which does things in their own way we have
to deal with these nuances smartly or end up reproducing the same
procedures each and every app does which imo is not maintainable.
> Thanks in advance,
>
> Alfonso Acosta
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth=
" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--=20
Luiz Augusto von Dentz