From: Matthias Kurz <m.kurz@irregular.at>
To: linux-bluetooth@vger.kernel.org
Cc: Bastien Nocera <hadess@hadess.net>,
Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Subject: [PATCH BlueZ v2 0/4] Add component batteries and Fast Pair Message Stream
Date: Fri, 21 Aug 2026 18:07:59 +0200 [thread overview]
Message-ID: <cover.1787327795.git.m.kurz@irregular.at> (raw)
True wireless earbuds can report separate charge states for the left bud,
right bud, and charging case. Battery1 currently has one fixed object per
Device1, so BlueZ cannot expose those values without collapsing them into
one percentage.
Extend the battery core and provider API to support child Battery1 objects
with a stable identifier, optional percentage, and optional charging state.
The existing Battery1 object at the Device1 path remains the aggregate
compatibility interface. Component objects and their new properties remain
experimental.
Add an experimental Fast Pair Message Stream profile. The Message Stream
specification defines both a fixed endpoint UUID over RFCOMM and an L2CAP PSM
transport:
https://developers.google.com/nearby/fast-pair/specifications/extensions/messagestream
This series implements the RFCOMM variant. Pixel Buds Pro advertise the fixed
UUID in SDP, from which the profile obtains the RFCOMM channel. L2CAP Message
Stream support is left for future work.
The Device Information extension defines battery update message group 0x03,
code 0x03, with three component bytes using the Battery Notification
encoding:
https://developers.google.com/nearby/fast-pair/specifications/extensions/deviceinformation
https://developers.google.com/nearby/fast-pair/specifications/extensions/batterynotification
The generic unknown-level status bit is retained for earbuds. The
TWS-specific unsupported-case value 0xff is treated as unavailable:
https://developers.google.com/nearby/fast-pair/specifications/devicefeaturerequirement/devicefeaturerequirement_hearables
If the Message Stream closes while BR/EDR remains connected, invalidate the
values and reconnect with exponential backoff. Reset the backoff only after
a battery-producing stream remains connected for the maximum backoff
interval, and do not retry permanent local errors. Once BR/EDR disappears,
cancel pending work and remove the component objects. The final patch adds a
standalone diagnostic and provider tool for interoperability testing.
Existing UPower versions continue to consume the aggregate Battery1 object.
When experimental BlueZ is enabled, they may log a coldplug warning for a
child object and ignore it because they expect Device1 at the same path. The
aggregate remains available, so the currently displayed value is preserved.
UPower support for component objects, including python-dbusmock integration
tests, is available here:
https://gitlab.freedesktop.org/mkurz/upower/-/commits/fastpair-multi-battery
It is based on:
https://gitlab.freedesktop.org/upower/upower/-/merge_requests/337
With the Bluetooth address anonymised, the object hierarchy observed with
Pixel Buds Pro was:
/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/battery_left
/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/battery_right
/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/battery_case
Each child implements org.bluez.Battery1. GetAll returned:
battery_left:
Percentage = 92
Source = "Fast Pair Message Stream"
Device = /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF
Identifier = "left"
Charging = false
battery_right:
Percentage = 90
Source = "Fast Pair Message Stream"
Device = /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF
Identifier = "right"
Charging = false
battery_case:
Source = "Fast Pair Message Stream"
Device = /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF
Identifier = "case"
Charging = false
Percentage was absent from the case object because its reported level was
unknown. This layout can be reproduced with python-dbusmock.
The complete path was tested through patched BlueZ, UPower, Solid,
PowerDevil, BlueZQt and BlueDevil. KDE Plasma's Power & Battery applet
displayed separate left, right and case batteries, including an unknown case
level, and the Bluetooth applet displayed all three components. Related KDE
changes are:
https://invent.kde.org/frameworks/solid/-/merge_requests/264
https://invent.kde.org/plasma/powerdevil/-/merge_requests/666
The series was tested with Pixel Buds Pro using an ASan/UBSan build. The live
tests covered fresh left/right/case reports, an unavailable case value,
explicit Message Stream disconnection, remote device disconnection,
reconnection, adapter power-down, and cancellation of a profile connection
in progress. Component properties were invalidated or removed as
appropriate, and the daemon reported no sanitizer failure.
The full 40-test make check suite passes under ASan/UBSan. The seven Fast
Pair parser tests provide 100% line coverage (55/55) and 95.83%
branch-direction coverage (46/48) for message-stream.c. They cover complete,
fragmented, coalesced/partial, zero-length and maximum-length frames, invalid
input, unknown-level status bits, the unavailable-case sentinel, and
reserved battery values.
Changes in v2:
- Clarify the RFCOMM transport and cite the protocol sources.
- Gate component objects and properties behind experimental mode.
- Document opaque component paths and the provider properties separately.
- Replace Message Stream magic numbers with shared named constants.
- Correct unknown-level and TWS unsupported-case decoding.
- Invalidate stale values and make disconnect handling reliable with
dual-mode devices.
- Add reconnect backoff, permanent-error handling, and safe pending-connect
cancellation.
- Improve the diagnostic provider's ObjectManager lifecycle and isolate it
per adapter.
- Expand parser tests and record their coverage in doc/test-coverage.txt.
v1:
https://lore.kernel.org/r/20260819223144.82045-1-m.kurz@irregular.at
Matthias Kurz (4):
battery: Add component battery objects
doc: Document component battery objects
fastpair: Add Message Stream battery profile
test: Add Fast Pair Message Stream tool
.gitignore | 1 +
Makefile.am | 8 +
Makefile.plugins | 5 +
Makefile.tools | 2 +-
doc/org.bluez.Battery.rst | 35 +-
doc/org.bluez.BatteryProvider.rst | 16 +
doc/test-coverage.txt | 3 +-
profiles/fastpair/fastpair.c | 650 +++++++++++++++++++++++++++++
profiles/fastpair/message-stream.c | 135 ++++++
profiles/fastpair/message-stream.h | 58 +++
src/battery.c | 401 +++++++++++++++---
src/battery.h | 4 +
test/test-fastpair | 568 +++++++++++++++++++++++++
unit/test-fastpair.c | 322 ++++++++++++++
14 files changed, 2146 insertions(+), 62 deletions(-)
create mode 100644 profiles/fastpair/fastpair.c
create mode 100644 profiles/fastpair/message-stream.c
create mode 100644 profiles/fastpair/message-stream.h
create mode 100755 test/test-fastpair
create mode 100644 unit/test-fastpair.c
base-commit: c73fa2f9ae2d366cb8a4f101fa9a5ccd9f33a4ea
--
2.55.0
next reply other threads:[~2026-08-21 16:08 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-21 16:07 Matthias Kurz [this message]
2026-08-21 16:08 ` [PATCH BlueZ v2 1/4] battery: Add component battery objects Matthias Kurz
2026-08-21 17:08 ` Add component batteries and Fast Pair Message Stream bluez.test.bot
2026-08-21 16:08 ` [PATCH BlueZ v2 2/4] doc: Document component battery objects Matthias Kurz
2026-08-21 16:08 ` [PATCH BlueZ v2 3/4] fastpair: Add Message Stream battery profile Matthias Kurz
2026-08-21 16:08 ` [PATCH BlueZ v2 4/4] test: Add Fast Pair Message Stream tool Matthias Kurz
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cover.1787327795.git.m.kurz@irregular.at \
--to=m.kurz@irregular.at \
--cc=hadess@hadess.net \
--cc=linux-bluetooth@vger.kernel.org \
--cc=luiz.dentz@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.