* [PATCH BlueZ v3 1/7] doc: Add org.bluez.Ranging1 documentation
2026-08-27 5:20 [PATCH BlueZ v3 0/7] Add ranging provider implementation Naga Bhavani Akella
@ 2026-08-27 5:20 ` Naga Bhavani Akella
2026-08-27 16:16 ` Add ranging provider implementation bluez.test.bot
2026-08-27 5:20 ` [PATCH BlueZ v3 2/7] doc: Add org.bluez.RangingProvider1 documentation Naga Bhavani Akella
` (6 subsequent siblings)
7 siblings, 1 reply; 10+ messages in thread
From: Naga Bhavani Akella @ 2026-08-27 5:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg,
Naga Bhavani Akella
Document org.bluez.Ranging1 interface, which
exposes the Distance estimate computed by an
external ranging provider registered via
RangingProviderManager.
Add org.bluez.ChannelSounding1.rst to EXTRA_DIST
---
Makefile.am | 9 ++++-
doc/org.bluez.Ranging1.rst | 78 ++++++++++++++++++++++++++++++++++++++
2 files changed, 85 insertions(+), 2 deletions(-)
create mode 100644 doc/org.bluez.Ranging1.rst
diff --git a/Makefile.am b/Makefile.am
index ab7457252..682ec26e5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -405,7 +405,8 @@ man_MANS += doc/org.bluez.Telephony.5 doc/org.bluez.Call.5
man_MANS += doc/org.bluez.ThermometerManager.5 \
doc/org.bluez.Thermometer.5 \
doc/org.bluez.ThermometerWatcher.5
-man_MANS += doc/org.bluez.ChannelSounding1.5
+man_MANS += doc/org.bluez.ChannelSounding1.5 \
+ doc/org.bluez.Ranging1.5
endif
manual_pages += doc/bluetoothd.8
manual_pages += doc/hci.7 doc/mgmt.7 doc/l2cap.7 doc/rfcomm.7 doc/sco.7 \
@@ -446,7 +447,8 @@ manual_pages += doc/org.bluez.Telephony.5 doc/org.bluez.Call.5
manual_pages += doc/org.bluez.ThermometerManager.5 \
doc/org.bluez.Thermometer.5 \
doc/org.bluez.ThermometerWatcher.5
-manual_pages += doc/org.bluez.ChannelSounding1.5
+manual_pages += doc/org.bluez.ChannelSounding1.5 \
+ doc/org.bluez.Ranging1.5
EXTRA_DIST += src/genbuiltin src/bluetooth.conf \
src/main.conf profiles/network/network.conf \
@@ -535,6 +537,9 @@ EXTRA_DIST += doc/org.bluez.ThermometerManager.rst \
doc/org.bluez.Thermometer.rst \
doc/org.bluez.ThermometerWatcher.rst
+EXTRA_DIST += doc/org.bluez.ChannelSounding1.rst \
+ doc/org.bluez.Ranging1.rst
+
EXTRA_DIST += doc/pics-opp.txt doc/pixit-opp.txt \
doc/pts-opp.txt
diff --git a/doc/org.bluez.Ranging1.rst b/doc/org.bluez.Ranging1.rst
new file mode 100644
index 000000000..8dce07e04
--- /dev/null
+++ b/doc/org.bluez.Ranging1.rst
@@ -0,0 +1,78 @@
+==================
+org.bluez.Ranging1
+==================
+
+--------------------------------------
+BlueZ D-Bus Ranging API documentation
+--------------------------------------
+
+:Version: BlueZ
+:Date: August 2026
+:Manual section: 5
+:Manual group: Linux System Administration
+
+Interface
+=========
+
+:Service: org.bluez
+:Interface: org.bluez.Ranging1
+:Object path: [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+Properties
+----------
+
+uint32 Distance [readonly]
+``````````````````````````
+
+Estimated distance to the remote device, in millimeters, as last
+reported by a ranging provider.
+
+Providers implementing **org.bluez.RangingProvider1(5)**, registered via
+**org.bluez.RangingProviderManager1(5)**, compute this value and report
+it back to **bluetoothd(8)** for reflection here. For Channel Sounding
+based ranging, a provider derives the distance from the raw procedure
+data reported via the **ProcedureData** signal on
+**org.bluez.ChannelSounding1(5)**, but this interface itself carries no
+assumption about the underlying ranging technology.
+
+This property emits ``PropertiesChanged`` whenever a new estimate is
+reported.
+
+This property does not exist until a ranging provider has reported at
+least one estimate for this device; reading it before then (e.g. via
+**org.freedesktop.DBus.Properties.Get**) fails with
+**org.freedesktop.DBus.Error.UnknownProperty**, and it is omitted from
+**GetAll**/**GetManagedObjects** results. Once a provider unregisters
+or stops providing ranging for this device, this whole
+**Ranging** interface — and therefore the **Distance** property — is
+removed (**InterfacesRemoved**); it does not linger with a stale value.
+
+Overview
+========
+
+**Ranging** is the read side of a three-party pattern ::
+
+ Ranging daemon -- RangingProvider --> BlueZ -- Ranging --> Desktop clients
+ (registered via RangingProviderManager)
+
+Sequence for a single distance update, once a provider is registered
+(see **org.bluez.RangingProviderManager1(5)**) and has exposed a
+**org.bluez.RangingProvider1(5)** object for the device (see
+**org.bluez.RangingProvider1(5)**)::
+
+ Ranging daemon bluetoothd Desktop client
+ | | |
+ | Distance = D on its | |
+ | RangingProvider object | |
+ | | |
+ | PropertiesChanged( | |
+ | RangingProvider, | |
+ | "Distance", D) | |
+ |-------------------------------->| |
+ | | updates Ranging on the |
+ | | device path with D |
+ | | |
+ | | PropertiesChanged( |
+ | | Ranging, |
+ | | "Distance", D) |
+ | |---------------------------->|
--
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH BlueZ v3 2/7] doc: Add org.bluez.RangingProvider1 documentation
2026-08-27 5:20 [PATCH BlueZ v3 0/7] Add ranging provider implementation Naga Bhavani Akella
2026-08-27 5:20 ` [PATCH BlueZ v3 1/7] doc: Add org.bluez.Ranging1 documentation Naga Bhavani Akella
@ 2026-08-27 5:20 ` Naga Bhavani Akella
2026-08-27 5:20 ` [PATCH BlueZ v3 3/7] doc: Add org.bluez.RangingProviderManager1 documentation Naga Bhavani Akella
` (5 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Naga Bhavani Akella @ 2026-08-27 5:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg,
Naga Bhavani Akella
Document org.bluez.RangingProvider1 interface,
implemented by external clients to expose per-device Channel Sounding
distance estimates that BlueZ reflects onto the corresponding device
object.
---
Makefile.am | 9 ++-
doc/org.bluez.RangingProvider1.rst | 92 ++++++++++++++++++++++++++++++
2 files changed, 98 insertions(+), 3 deletions(-)
create mode 100644 doc/org.bluez.RangingProvider1.rst
diff --git a/Makefile.am b/Makefile.am
index 682ec26e5..8252df65b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -406,7 +406,8 @@ man_MANS += doc/org.bluez.ThermometerManager.5 \
doc/org.bluez.Thermometer.5 \
doc/org.bluez.ThermometerWatcher.5
man_MANS += doc/org.bluez.ChannelSounding1.5 \
- doc/org.bluez.Ranging1.5
+ doc/org.bluez.Ranging1.5 \
+ doc/org.bluez.RangingProvider1.5
endif
manual_pages += doc/bluetoothd.8
manual_pages += doc/hci.7 doc/mgmt.7 doc/l2cap.7 doc/rfcomm.7 doc/sco.7 \
@@ -448,7 +449,8 @@ manual_pages += doc/org.bluez.ThermometerManager.5 \
doc/org.bluez.Thermometer.5 \
doc/org.bluez.ThermometerWatcher.5
manual_pages += doc/org.bluez.ChannelSounding1.5 \
- doc/org.bluez.Ranging1.5
+ doc/org.bluez.Ranging1.5 \
+ doc/org.bluez.RangingProvider1.5
EXTRA_DIST += src/genbuiltin src/bluetooth.conf \
src/main.conf profiles/network/network.conf \
@@ -538,7 +540,8 @@ EXTRA_DIST += doc/org.bluez.ThermometerManager.rst \
doc/org.bluez.ThermometerWatcher.rst
EXTRA_DIST += doc/org.bluez.ChannelSounding1.rst \
- doc/org.bluez.Ranging1.rst
+ doc/org.bluez.Ranging1.rst \
+ doc/org.bluez.RangingProvider1.rst
EXTRA_DIST += doc/pics-opp.txt doc/pixit-opp.txt \
doc/pts-opp.txt
diff --git a/doc/org.bluez.RangingProvider1.rst b/doc/org.bluez.RangingProvider1.rst
new file mode 100644
index 000000000..4237d5a5e
--- /dev/null
+++ b/doc/org.bluez.RangingProvider1.rst
@@ -0,0 +1,92 @@
+===========================
+org.bluez.RangingProvider1
+===========================
+
+-----------------------------------------------
+BlueZ D-Bus RangingProvider API documentation
+-----------------------------------------------
+
+:Version: BlueZ
+:Date: August 2026
+:Manual section: 5
+:Manual group: Linux System Administration
+
+Interface
+=========
+
+:Service: <client D-Bus address>
+:Interface: org.bluez.RangingProvider1
+:Object path: {provider_root}/{unique ranging object path}
+
+Properties
+----------
+
+object Device [readonly]
+```````````````````````````
+
+The object path of the device that this distance estimate applies to.
+
+uint32 Distance [readonly, optional]
+````````````````````````````````````
+
+Estimated distance to the device identified by **Device**, in
+millimeters. May be absent when the object is first exposed and set
+once the provider has computed an initial estimate; **bluetoothd(8)**
+watches for it via ``PropertiesChanged``.
+
+Error handling
+==============
+
+This interface has no methods; it is implemented by the ranging daemon
+and consumed by **bluetoothd(8)** via **org.freedesktop.DBus.ObjectManager**
+and property-change watches, so there is no D-Bus method call to return
+an error from. Instead, **bluetoothd(8)** validates each exposed object
+and silently ignores (logging a warning) any that fail validation:
+
+- An object path outside the provider's registered root path is
+ ignored.
+- An object missing the **Device** property, or whose **Device**
+ property is not an object path, is ignored.
+- An object whose **Device** does not match an existing, non-temporary
+ device is ignored.
+- A **Distance** value (initial or updated) that is not a uint32 is
+ ignored; the previously reported value, if any, is left unchanged.
+- If the provider has already exposed a **RangingProvider** object
+ for a given device (e.g. a stale or duplicate object), any further
+ object it exposes for that same device is ignored — each device may
+ only be represented by one **RangingProvider** object at a time.
+ Note that only one provider may be registered per adapter at all
+ (see **org.bluez.RangingProviderManager1(5)**), so this situation can
+ only arise from the same provider re-exposing a device, not from a
+ second, distinct provider racing for it.
+
+Removing an ignored object, or fixing it and re-adding it, is treated
+the same as any other **InterfacesAdded**/**InterfacesRemoved** event.
+
+Overview
+========
+
+A ranging daemon exposes one **RangingProvider** object per device it
+is tracking, under the root path it registered with
+**org.bluez.RangingProviderManager1(5)**::
+
+ Ranging daemon bluetoothd
+ | |
+ | export {provider_root}/dev_XX (RangingProvider) |
+ | { Device: <device path> } |
+ |--------------------------------------------------->|
+ | | reflects a
+ | | Ranging object
+ | | on the device path
+ | |
+ | Distance = D |
+ | PropertiesChanged("Distance", D) |
+ |--------------------------------------------------->|
+ | | updates
+ | | Ranging.Distance
+ | | and notifies clients
+ | | (see org.bluez.Ranging1(5))
+
+Removing the object (or disconnecting from D-Bus) causes
+**bluetoothd(8)** to remove the matching **org.bluez.Ranging1(5)**
+interface from the device.
--
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH BlueZ v3 3/7] doc: Add org.bluez.RangingProviderManager1 documentation
2026-08-27 5:20 [PATCH BlueZ v3 0/7] Add ranging provider implementation Naga Bhavani Akella
2026-08-27 5:20 ` [PATCH BlueZ v3 1/7] doc: Add org.bluez.Ranging1 documentation Naga Bhavani Akella
2026-08-27 5:20 ` [PATCH BlueZ v3 2/7] doc: Add org.bluez.RangingProvider1 documentation Naga Bhavani Akella
@ 2026-08-27 5:20 ` Naga Bhavani Akella
2026-08-27 5:20 ` [PATCH BlueZ v3 4/7] doc: Modify bluetoothctl-cs documentation Naga Bhavani Akella
` (4 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Naga Bhavani Akella @ 2026-08-27 5:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg,
Naga Bhavani Akella
Document org.bluez.RangingProviderManager1 interface,
used by external clients to register and unregister
themselves as Channel Sounding distance providers via
RegisterRangingProvider/UnregisterRangingProvider
---
Makefile.am | 9 ++-
doc/org.bluez.RangingProviderManager1.rst | 91 +++++++++++++++++++++++
2 files changed, 97 insertions(+), 3 deletions(-)
create mode 100644 doc/org.bluez.RangingProviderManager1.rst
diff --git a/Makefile.am b/Makefile.am
index 8252df65b..b3b7077fd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -407,7 +407,8 @@ man_MANS += doc/org.bluez.ThermometerManager.5 \
doc/org.bluez.ThermometerWatcher.5
man_MANS += doc/org.bluez.ChannelSounding1.5 \
doc/org.bluez.Ranging1.5 \
- doc/org.bluez.RangingProvider1.5
+ doc/org.bluez.RangingProvider1.5 \
+ doc/org.bluez.RangingProviderManager1.5
endif
manual_pages += doc/bluetoothd.8
manual_pages += doc/hci.7 doc/mgmt.7 doc/l2cap.7 doc/rfcomm.7 doc/sco.7 \
@@ -450,7 +451,8 @@ manual_pages += doc/org.bluez.ThermometerManager.5 \
doc/org.bluez.ThermometerWatcher.5
manual_pages += doc/org.bluez.ChannelSounding1.5 \
doc/org.bluez.Ranging1.5 \
- doc/org.bluez.RangingProvider1.5
+ doc/org.bluez.RangingProvider1.5 \
+ doc/org.bluez.RangingProviderManager1.5
EXTRA_DIST += src/genbuiltin src/bluetooth.conf \
src/main.conf profiles/network/network.conf \
@@ -541,7 +543,8 @@ EXTRA_DIST += doc/org.bluez.ThermometerManager.rst \
EXTRA_DIST += doc/org.bluez.ChannelSounding1.rst \
doc/org.bluez.Ranging1.rst \
- doc/org.bluez.RangingProvider1.rst
+ doc/org.bluez.RangingProvider1.rst \
+ doc/org.bluez.RangingProviderManager1.rst
EXTRA_DIST += doc/pics-opp.txt doc/pixit-opp.txt \
doc/pts-opp.txt
diff --git a/doc/org.bluez.RangingProviderManager1.rst b/doc/org.bluez.RangingProviderManager1.rst
new file mode 100644
index 000000000..da551c981
--- /dev/null
+++ b/doc/org.bluez.RangingProviderManager1.rst
@@ -0,0 +1,91 @@
+======================================
+org.bluez.RangingProviderManager1
+======================================
+
+------------------------------------------------------
+BlueZ D-Bus RangingProviderManager API documentation
+------------------------------------------------------
+
+:Version: BlueZ
+:Date: August 2026
+:Manual section: 5
+:Manual group: Linux System Administration
+
+Description
+===========
+
+A ranging provider starts by registering itself with the
+**RegisterRangingProvider()** method, passing an object path as the
+provider ID. Then, it can start exposing **org.bluez.RangingProvider1(5)**
+objects having a path starting with the given provider ID. It can also
+remove objects at any time.
+The objects and their properties exposed by ranging providers will be
+reflected on the matching **org.bluez.Ranging1(5)** interface.
+
+**bluetoothd(8)** will stop monitoring these exposed and removed objects
+after **UnregisterRangingProvider()** is called for that provider ID.
+
+Interface
+=========
+
+:Service: org.bluez
+:Interface: org.bluez.RangingProviderManager1
+:Object path: /org/bluez/{hci0,hci1,...}
+
+Methods
+-------
+
+void RegisterRangingProvider(object provider)
+`````````````````````````````````````````````````
+
+Registers a ranging provider. A registered ranging provider can then
+expose objects with **org.bluez.RangingProvider1(5)** interface.
+
+Only one ranging provider may be registered per adapter at a time: the
+object path above is per-adapter, so a provider registered on hci0 does
+not block registration on hci1. If a provider is already registered on
+that adapter, this call fails with **org.bluez.Error.AlreadyExists**,
+regardless of which client registered first.
+
+Possible errors:
+
+- org.bluez.Error.InvalidArguments
+- org.bluez.Error.AlreadyExists
+- org.bluez.Error.Failed
+
+void UnregisterRangingProvider(object provider)
+```````````````````````````````````````````````````
+
+Unregisters a ranging provider previously registered with
+**RegisterRangingProvider()**. After unregistration, the
+**org.bluez.RangingProvider1(5)** objects provided by this client are
+ignored by **bluetoothd(8)**.
+
+Fails if no provider is registered at the given path, or if the caller
+is not the D-Bus client that registered it.
+
+Possible errors:
+
+- org.bluez.Error.InvalidArguments
+- org.bluez.Error.DoesNotExist
+
+Overview
+========
+
+::
+
+ Ranging daemon bluetoothd
+ | |
+ | RegisterRangingProvider(provider_root) |
+ |---------------------------------------------->|
+ | (ok) |
+ |<----------------------------------------------|
+ | |
+ | ... expose/update RangingProvider objects |
+ | under provider_root (see |
+ | org.bluez.RangingProvider1(5)) ... |
+ | |
+ | UnregisterRangingProvider(provider_root) |
+ |---------------------------------------------->|
+ | (ok) |
+ |<----------------------------------------------|
--
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH BlueZ v3 4/7] doc: Modify bluetoothctl-cs documentation
2026-08-27 5:20 [PATCH BlueZ v3 0/7] Add ranging provider implementation Naga Bhavani Akella
` (2 preceding siblings ...)
2026-08-27 5:20 ` [PATCH BlueZ v3 3/7] doc: Add org.bluez.RangingProviderManager1 documentation Naga Bhavani Akella
@ 2026-08-27 5:20 ` Naga Bhavani Akella
2026-08-27 5:20 ` [PATCH BlueZ v3 5/7] src: Add Ranging provider D-Bus API Naga Bhavani Akella
` (3 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Naga Bhavani Akella @ 2026-08-27 5:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg,
Naga Bhavani Akella
Add documentation for register-provider and
unregister-provider cs menu options
---
doc/bluetoothctl-cs.rst | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/doc/bluetoothctl-cs.rst b/doc/bluetoothctl-cs.rst
index e49b4ea08..86be5f519 100644
--- a/doc/bluetoothctl-cs.rst
+++ b/doc/bluetoothctl-cs.rst
@@ -122,6 +122,47 @@ The parameter output is divided into three sections:
:Example Show active session and all CS parameters:
| **> show**
+register_provider
+-----------------
+
+**Reference implementation only** — not a real ranging algorithm. Registers
+bluetoothctl itself as a Channel Sounding ranging provider via
+**RegisterRangingProvider()**, then listens for **ProcedureData** signals on
+**org.bluez.ChannelSounding1(5)** and turns each one into a bounded,
+illustrative distance estimate (not an accurate measurement) exposed via
+**org.bluez.RangingProvider1(5)** — visible on the device's
+**org.bluez.Ranging1(5)** **Distance** property once bluetoothd reflects it.
+
+The provider path defaults to ``/``, the only path bluetoothctl exposes an
+``ObjectManager`` at; a different path will register but bluetoothd will not
+discover any objects under it. See **test/example-ranging-provider** for a
+more realistic standalone daemon built the same way.
+
+Only one ranging provider may be registered per adapter at a time. If a
+real ranging daemon is already registered, this command fails with
+**AlreadyExists**; conversely, a real daemon cannot register while
+bluetoothctl's reference provider is active. Run **unregister_provider**
+(or stop the other provider) first.
+
+:Usage: **> register_provider [path]**
+:Uses: **org.bluez.RangingProviderManager1(5)** method
+ **RegisterRangingProvider**
+:[path]: Provider root object path (optional; default ``/``)
+:Example Register with the default path:
+ | **> register_provider**
+
+unregister_provider
+--------------------
+
+Unregisters the ranging provider previously registered with
+**register_provider**, removing its exposed **RangingProvider** objects.
+
+:Usage: **> unregister_provider**
+:Uses: **org.bluez.RangingProviderManager1(5)** method
+ **UnregisterRangingProvider**
+:Example Unregister the provider:
+ | **> unregister_provider**
+
CS Parameter Commands
======================
--
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH BlueZ v3 5/7] src: Add Ranging provider D-Bus API
2026-08-27 5:20 [PATCH BlueZ v3 0/7] Add ranging provider implementation Naga Bhavani Akella
` (3 preceding siblings ...)
2026-08-27 5:20 ` [PATCH BlueZ v3 4/7] doc: Modify bluetoothctl-cs documentation Naga Bhavani Akella
@ 2026-08-27 5:20 ` Naga Bhavani Akella
2026-08-27 5:20 ` [PATCH BlueZ v3 6/7] client: Add ranging distance display support to bluetoothctl Naga Bhavani Akella
` (2 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Naga Bhavani Akella @ 2026-08-27 5:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg,
Naga Bhavani Akella
Introduce org.bluez.Ranging1 and
org.bluez.RangingProvider1/ RangingProviderManager1
D-Bus interfaces
External providers can register a root path
via RegisterDistanceProvider and expose per-device
RangingProvider objects; updates to their Distance
property are mirrored onto exported Ranging objects via
btd_ranging_update(), emitting a PropertiesChanged signal.
The Ranging provider manager is wired into the adapter lifecycle,
created on adapter setup and destroyed on adapter
removal.
---
Makefile.am | 1 +
src/adapter.c | 11 +
src/ranging.c | 644 ++++++++++++++++++++++++++++++++++++++++++++++++++
src/ranging.h | 25 ++
4 files changed, 681 insertions(+)
create mode 100644 src/ranging.c
create mode 100644 src/ranging.h
diff --git a/Makefile.am b/Makefile.am
index b3b7077fd..b85b2d8d9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -332,6 +332,7 @@ bluetoothd_internal_sources = \
src/eir.h src/eir.c \
src/adv_monitor.h src/adv_monitor.c \
src/battery.h src/battery.c \
+ src/ranging.h src/ranging.c \
src/settings.h src/settings.c \
src/set.h src/set.c \
src/bearer.h src/bearer.c
diff --git a/src/adapter.c b/src/adapter.c
index cf59db4aa..8c4678f07 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -67,6 +67,7 @@
#include "adv_monitor.h"
#include "eir.h"
#include "battery.h"
+#include "ranging.h"
#define MODE_OFF 0x00
#define MODE_CONNECTABLE 0x01
@@ -336,6 +337,8 @@ struct btd_adapter {
struct btd_battery_provider_manager *battery_provider_manager;
+ struct btd_ranging_provider_manager *ranging_provider_manager;
+
GHashTable *allowed_uuid_set; /* Set of allowed service UUIDs */
gboolean initialized;
@@ -7196,6 +7199,7 @@ static void adapter_remove(struct btd_adapter *adapter)
{
GSList *l;
struct gatt_db *db;
+ struct btd_ranging_provider_manager *ranging_manager;
DBG("Removing adapter %s", adapter->path);
@@ -7230,6 +7234,10 @@ static void adapter_remove(struct btd_adapter *adapter)
btd_battery_provider_manager_destroy(adapter->battery_provider_manager);
adapter->battery_provider_manager = NULL;
+ ranging_manager = adapter->ranging_provider_manager;
+ btd_ranging_provider_manager_destroy(ranging_manager);
+ adapter->ranging_provider_manager = NULL;
+
g_slist_free(adapter->pin_callbacks);
adapter->pin_callbacks = NULL;
@@ -9645,6 +9653,9 @@ static int adapter_register(struct btd_adapter *adapter)
adapter->battery_provider_manager =
btd_battery_provider_manager_create(adapter);
+ adapter->ranging_provider_manager =
+ btd_ranging_provider_manager_create(adapter);
+
/* Don't start GATT database and advertising managers on
* non-LE controllers.
*/
diff --git a/src/ranging.c b/src/ranging.c
new file mode 100644
index 000000000..c25381779
--- /dev/null
+++ b/src/ranging.c
@@ -0,0 +1,644 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <glib.h>
+
+#include "gdbus/gdbus.h"
+#include "bluetooth/bluetooth.h"
+#include "src/shared/queue.h"
+#include "src/shared/util.h"
+#include "ranging.h"
+#include "dbus-common.h"
+#include "adapter.h"
+#include "device.h"
+#include "log.h"
+#include "error.h"
+
+#define RANGING_INTERFACE "org.bluez.Ranging1"
+#define RANGING_PROVIDER_INTERFACE "org.bluez.RangingProvider1"
+#define RANGING_PROVIDER_MANAGER_INTERFACE \
+ "org.bluez.RangingProviderManager1"
+
+struct btd_ranging {
+ struct btd_ranging_provider_manager *manager; /* Does not own pointer */
+ char *path; /* D-Bus object path */
+ uint32_t distance_millimeters; /* Estimated distance to remote device */
+ bool has_value; /* false until the first estimate is reported */
+ char *provider_path; /* The provider root path, if any */
+};
+
+struct btd_ranging_provider_manager {
+ struct btd_adapter *adapter; /* Does not own pointer */
+ struct queue *providers;
+ struct queue *rangings;
+};
+
+struct ranging_provider {
+ struct btd_ranging_provider_manager *manager; /* Does not own */
+
+ char *owner; /* Owner D-Bus address */
+ char *path; /* D-Bus object path */
+
+ GDBusClient *client;
+};
+
+static void provider_disconnect_cb(DBusConnection *conn, void *user_data);
+
+static void ranging_add(struct btd_ranging *ranging)
+{
+ queue_push_head(ranging->manager->rangings, ranging);
+}
+
+static void ranging_remove(struct btd_ranging *ranging)
+{
+ queue_remove(ranging->manager->rangings, ranging);
+}
+
+static bool match_path(const void *data, const void *user_data)
+{
+ const struct btd_ranging *ranging = data;
+ const char *path = user_data;
+
+ return g_strcmp0(ranging->path, path) == 0;
+}
+
+static struct btd_ranging *ranging_new(
+ struct btd_ranging_provider_manager *manager,
+ const char *path, const char *provider_path)
+{
+ struct btd_ranging *ranging;
+
+ ranging = new0(struct btd_ranging, 1);
+ ranging->manager = manager;
+ ranging->path = g_strdup(path);
+ if (provider_path)
+ ranging->provider_path = g_strdup(provider_path);
+
+ return ranging;
+}
+
+static void ranging_free(struct btd_ranging *ranging)
+{
+ g_free(ranging->path);
+ g_free(ranging->provider_path);
+
+ free(ranging);
+}
+
+static gboolean property_distance_get(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct btd_ranging *ranging = data;
+
+ return dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32,
+ &ranging->distance_millimeters);
+}
+
+static gboolean property_distance_exists(const GDBusPropertyTable *property,
+ void *data)
+{
+ struct btd_ranging *ranging = data;
+
+ return ranging->has_value;
+}
+
+static const GDBusPropertyTable ranging_properties[] = {
+ { "Distance", "u", property_distance_get, NULL,
+ property_distance_exists },
+ {}
+};
+
+struct btd_ranging *btd_ranging_register(
+ struct btd_ranging_provider_manager *manager,
+ const char *path, const char *provider_path)
+{
+ struct btd_ranging *ranging;
+
+ DBG("path = %s", path);
+
+ if (queue_find(manager->rangings, match_path, path)) {
+ error("error registering ranging: path exists");
+ return NULL;
+ }
+
+ if (!g_str_has_prefix(path, "/")) {
+ error("error registering ranging: "
+ "invalid D-Bus object path");
+ return NULL;
+ }
+
+ ranging = ranging_new(manager, path, provider_path);
+ ranging_add(ranging);
+
+ if (!g_dbus_register_interface(btd_get_dbus_connection(),
+ ranging->path, RANGING_INTERFACE,
+ NULL, NULL, ranging_properties,
+ ranging, NULL)) {
+ error("error registering D-Bus interface for %s",
+ ranging->path);
+
+ ranging_remove(ranging);
+ ranging_free(ranging);
+
+ return NULL;
+ }
+
+ DBG("registered Ranging object: %s", ranging->path);
+
+ return ranging;
+}
+
+bool btd_ranging_unregister(struct btd_ranging *ranging)
+{
+ bool ret = true;
+
+ DBG("path = %s", ranging->path);
+
+ if (!queue_find(ranging->manager->rangings, NULL, ranging)) {
+ error("error unregistering ranging: "
+ "ranging %s is not registered",
+ ranging->path);
+ return false;
+ }
+
+ if (!g_dbus_unregister_interface(btd_get_dbus_connection(),
+ ranging->path,
+ RANGING_INTERFACE)) {
+ error("error unregistering ranging %s from "
+ "D-Bus interface", ranging->path);
+ ret = false;
+ }
+
+ ranging_remove(ranging);
+ ranging_free(ranging);
+
+ return ret;
+}
+
+bool btd_ranging_update(struct btd_ranging *ranging,
+ uint32_t distance_millimeters)
+{
+ DBG("path = %s", ranging->path);
+
+ if (!queue_find(ranging->manager->rangings, NULL, ranging)) {
+ error("error updating ranging: ranging is not "
+ "registered");
+ return false;
+ }
+
+ ranging->distance_millimeters = distance_millimeters;
+ ranging->has_value = true;
+
+ DBG("Ranging distance updated: path = %s, millimeters = %u",
+ ranging->path, distance_millimeters);
+
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ ranging->path, RANGING_INTERFACE,
+ "Distance");
+
+ return true;
+}
+
+static struct btd_ranging *find_ranging_by_path(
+ struct btd_ranging_provider_manager *manager, const char *path)
+{
+ return queue_find(manager->rangings, match_path, path);
+}
+
+static void provider_distance_property_changed_cb(GDBusProxy *proxy,
+ const char *name,
+ DBusMessageIter *iter,
+ void *user_data)
+{
+ struct ranging_provider *provider = user_data;
+ struct btd_ranging *ranging;
+ uint32_t distance_millimeters = 0;
+ const char *export_path;
+ const char *path = g_dbus_proxy_get_path(proxy);
+ DBusMessageIter dev_iter;
+
+ if (strcmp(name, "Distance") != 0)
+ return;
+
+ if (iter) {
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32) {
+ warn("Ignoring Distance update on %s: "
+ "expected type uint32", path);
+ return;
+ }
+
+ dbus_message_iter_get_basic(iter, &distance_millimeters);
+ }
+
+ if (g_dbus_proxy_get_property(proxy, "Device", &dev_iter) == FALSE) {
+ warn("Ignoring Distance update on %s: "
+ "no Device property", path);
+ return;
+ }
+
+ if (dbus_message_iter_get_arg_type(&dev_iter) !=
+ DBUS_TYPE_OBJECT_PATH) {
+ warn("Ignoring Distance update on %s: "
+ "invalid Device property type", path);
+ return;
+ }
+
+ dbus_message_iter_get_basic(&dev_iter, &export_path);
+
+ DBG("Ranging distance changed on %s, millimeters = %u",
+ path, distance_millimeters);
+
+ ranging = find_ranging_by_path(provider->manager, export_path);
+ if (!ranging)
+ return;
+
+ btd_ranging_update(ranging, distance_millimeters);
+}
+
+static void provider_distance_added_cb(GDBusProxy *proxy, void *user_data)
+{
+ struct ranging_provider *provider = user_data;
+ struct btd_ranging *ranging;
+ struct btd_device *device;
+ const char *path = g_dbus_proxy_get_path(proxy);
+ const char *export_path;
+ uint32_t distance_millimeters;
+ DBusMessageIter iter;
+
+ if (strcmp(g_dbus_proxy_get_interface(proxy),
+ RANGING_PROVIDER_INTERFACE) != 0)
+ return;
+
+ if (!g_str_has_prefix(path, provider->path)) {
+ warn("Ignoring ranging object %s outside provider root %s",
+ path, provider->path);
+ return;
+ }
+
+ if (g_dbus_proxy_get_property(proxy, "Device", &iter) == FALSE) {
+ warn("Ranging object %s does not specify device path",
+ path);
+ return;
+ }
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) {
+ warn("Ranging object %s has invalid Device property type",
+ path);
+ return;
+ }
+
+ dbus_message_iter_get_basic(&iter, &export_path);
+
+ device = btd_adapter_find_device_by_path(provider->manager->adapter,
+ export_path);
+ if (!device || device_is_temporary(device)) {
+ warn("Ignoring non-existent device path for ranging %s",
+ export_path);
+ return;
+ }
+
+ if (find_ranging_by_path(provider->manager, export_path)) {
+ DBG("Ranging for %s is already provided, ignoring the "
+ "new one", export_path);
+ return;
+ }
+
+ ranging = btd_ranging_register(provider->manager, export_path,
+ provider->path);
+ if (!ranging)
+ return;
+
+ g_dbus_proxy_set_property_watch(proxy,
+ provider_distance_property_changed_cb, provider);
+
+ DBG("provided ranging added %s", path);
+
+ /* Distance property may not be immediately available,
+ * that's okay since we monitor changes to this property.
+ */
+ if (g_dbus_proxy_get_property(proxy, "Distance",
+ &iter) == FALSE)
+ return;
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) {
+ warn("Ignoring Distance property on %s: "
+ "expected type uint32", path);
+ return;
+ }
+
+ dbus_message_iter_get_basic(&iter, &distance_millimeters);
+
+ btd_ranging_update(ranging, distance_millimeters);
+}
+
+static void provider_distance_removed_cb(GDBusProxy *proxy, void *user_data)
+{
+ struct ranging_provider *provider = user_data;
+ struct btd_ranging *ranging;
+ const char *export_path;
+ DBusMessageIter iter;
+
+ if (strcmp(g_dbus_proxy_get_interface(proxy),
+ RANGING_PROVIDER_INTERFACE) != 0)
+ return;
+
+ if (g_dbus_proxy_get_property(proxy, "Device", &iter) == FALSE)
+ return;
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH)
+ return;
+
+ dbus_message_iter_get_basic(&iter, &export_path);
+
+ DBG("provided ranging removed %s", g_dbus_proxy_get_path(proxy));
+
+ ranging = find_ranging_by_path(provider->manager, export_path);
+ if (!ranging)
+ return;
+
+ if (g_strcmp0(ranging->provider_path, provider->path) != 0)
+ return;
+
+ g_dbus_proxy_set_property_watch(proxy, NULL, NULL);
+
+ btd_ranging_unregister(ranging);
+}
+
+static bool match_provider_path(const void *data, const void *user_data)
+{
+ const struct ranging_provider *provider = data;
+ const char *path = user_data;
+
+ return strcmp(provider->path, path) == 0;
+}
+
+static bool match_ranging_provider_path(const void *data,
+ const void *user_data)
+{
+ const struct btd_ranging *ranging = data;
+ const struct ranging_provider *provider = user_data;
+
+ return g_strcmp0(ranging->provider_path, provider->path) == 0;
+}
+
+static void ranging_destroy(void *data)
+{
+ struct btd_ranging *ranging = data;
+
+ g_dbus_unregister_interface(btd_get_dbus_connection(),
+ ranging->path, RANGING_INTERFACE);
+ ranging_free(ranging);
+}
+
+static void ranging_destroy_warn(void *data)
+{
+ struct btd_ranging *ranging = data;
+
+ warn("Ranging %s still registered at manager destroy",
+ ranging->path);
+
+ ranging_destroy(data);
+}
+
+static void ranging_provider_free(gpointer data)
+{
+ struct ranging_provider *provider = data;
+
+ /* Unregister rangings under the root path of provider->path */
+ queue_remove_all(provider->manager->rangings,
+ match_ranging_provider_path, provider,
+ ranging_destroy);
+
+ g_free(provider->owner);
+ g_free(provider->path);
+
+ if (provider->client) {
+ g_dbus_client_set_disconnect_watch(provider->client, NULL,
+ NULL);
+ g_dbus_client_set_proxy_handlers(provider->client, NULL, NULL,
+ NULL, NULL);
+ g_dbus_client_unref(provider->client);
+ }
+
+ free(provider);
+}
+
+static struct ranging_provider *
+ranging_provider_new(DBusConnection *conn,
+ struct btd_ranging_provider_manager *manager,
+ const char *path, const char *sender)
+{
+ struct ranging_provider *provider;
+
+ provider = new0(struct ranging_provider, 1);
+ provider->manager = manager;
+ provider->owner = g_strdup(sender);
+ provider->path = g_strdup(path);
+
+ provider->client = g_dbus_client_new_full(conn, sender, path, path);
+
+ if (!provider->client) {
+ error("error creating D-Bus client %s", path);
+ ranging_provider_free(provider);
+ return NULL;
+ }
+
+ g_dbus_client_set_disconnect_watch(provider->client,
+ provider_disconnect_cb, provider);
+
+ g_dbus_client_set_proxy_handlers(provider->client,
+ provider_distance_added_cb,
+ provider_distance_removed_cb, NULL,
+ provider);
+
+ return provider;
+}
+
+static void provider_disconnect_cb(DBusConnection *conn, void *user_data)
+{
+ struct ranging_provider *provider = user_data;
+ struct btd_ranging_provider_manager *manager = provider->manager;
+
+ DBG("Ranging provider client disconnected %s root path %s",
+ provider->owner, provider->path);
+
+ if (!queue_find(manager->providers, NULL, provider)) {
+ warn("Disconnection on a non-existing provider %s",
+ provider->path);
+ return;
+ }
+
+ queue_remove(manager->providers, provider);
+ ranging_provider_free(provider);
+}
+
+static DBusMessage *register_ranging_provider(DBusConnection *conn,
+ DBusMessage *msg,
+ void *user_data)
+{
+ struct btd_ranging_provider_manager *manager = user_data;
+ const char *sender = dbus_message_get_sender(msg);
+ DBusMessageIter args;
+ const char *path;
+ struct ranging_provider *provider;
+
+ if (!dbus_message_iter_init(msg, &args))
+ return btd_error_invalid_args(msg);
+
+ if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(&args, &path);
+
+ DBG("register ranging provider path = %s", path);
+
+ if (!g_str_has_prefix(path, "/"))
+ return btd_error_invalid_args(msg);
+
+ /* Only one ranging provider may be registered at a time: allowing
+ * concurrent providers means whichever claims a device first wins,
+ * silently locking out any other provider for that device. Reject
+ * up front instead, with a clear error, regardless of which side
+ * (bluetoothctl's reference provider or a real ranging daemon)
+ * registered first.
+ */
+ if (!queue_isempty(manager->providers)) {
+ return dbus_message_new_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Provider already exists");
+ }
+
+ provider = ranging_provider_new(conn, manager, path, sender);
+ if (!provider)
+ return btd_error_failed(msg, "failed to create provider");
+
+ queue_push_head(manager->providers, provider);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_ranging_provider(DBusConnection *conn,
+ DBusMessage *msg,
+ void *user_data)
+{
+ struct btd_ranging_provider_manager *manager = user_data;
+ const char *sender = dbus_message_get_sender(msg);
+ DBusMessageIter args;
+ const char *path;
+ struct ranging_provider *provider;
+
+ if (!dbus_message_iter_init(msg, &args))
+ return btd_error_invalid_args(msg);
+
+ if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(&args, &path);
+
+ DBG("unregister ranging provider path = %s", path);
+
+ provider = queue_find(manager->providers, match_provider_path, path);
+ if (!provider || strcmp(provider->owner, sender) != 0) {
+ return dbus_message_new_error(msg,
+ ERROR_INTERFACE ".DoesNotExist",
+ "Provider does not exist");
+ }
+
+ queue_remove(manager->providers, provider);
+ ranging_provider_free(provider);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static const GDBusMethodTable methods[] = {
+ { GDBUS_METHOD("RegisterRangingProvider",
+ GDBUS_ARGS({ "provider", "o" }), NULL,
+ register_ranging_provider) },
+ { GDBUS_METHOD("UnregisterRangingProvider",
+ GDBUS_ARGS({ "provider", "o" }), NULL,
+ unregister_ranging_provider) },
+ {}
+};
+
+static struct btd_ranging_provider_manager *
+manager_new(struct btd_adapter *adapter)
+{
+ struct btd_ranging_provider_manager *manager;
+
+ DBG("");
+
+ manager = new0(struct btd_ranging_provider_manager, 1);
+ manager->adapter = adapter;
+ manager->providers = queue_new();
+ manager->rangings = queue_new();
+
+ return manager;
+}
+
+static void manager_free(struct btd_ranging_provider_manager *manager)
+{
+ if (!manager)
+ return;
+
+ DBG("");
+
+ queue_destroy(manager->providers, ranging_provider_free);
+ queue_destroy(manager->rangings, ranging_destroy_warn);
+
+ free(manager);
+}
+
+struct btd_ranging_provider_manager *
+btd_ranging_provider_manager_create(struct btd_adapter *adapter)
+{
+ struct btd_ranging_provider_manager *manager;
+
+ if (!adapter)
+ return NULL;
+
+ manager = manager_new(adapter);
+ if (!manager)
+ return NULL;
+
+ if (!g_dbus_register_interface(btd_get_dbus_connection(),
+ adapter_get_path(manager->adapter),
+ RANGING_PROVIDER_MANAGER_INTERFACE,
+ methods, NULL, NULL, manager, NULL)) {
+ error("error registering "
+ RANGING_PROVIDER_MANAGER_INTERFACE " interface");
+ manager_free(manager);
+ return NULL;
+ }
+
+ info("Ranging Provider Manager created");
+
+ return manager;
+}
+
+void btd_ranging_provider_manager_destroy(
+ struct btd_ranging_provider_manager *manager)
+{
+ if (!manager)
+ return;
+
+ g_dbus_unregister_interface(btd_get_dbus_connection(),
+ adapter_get_path(manager->adapter),
+ RANGING_PROVIDER_MANAGER_INTERFACE);
+
+ info("Ranging Provider Manager destroyed");
+
+ manager_free(manager);
+}
diff --git a/src/ranging.h b/src/ranging.h
new file mode 100644
index 000000000..79ff6cc41
--- /dev/null
+++ b/src/ranging.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ *
+ */
+
+struct btd_adapter;
+struct btd_ranging;
+struct btd_ranging_provider_manager;
+
+struct btd_ranging *btd_ranging_register(
+ struct btd_ranging_provider_manager *manager,
+ const char *path, const char *provider_path);
+bool btd_ranging_unregister(struct btd_ranging *ranging);
+bool btd_ranging_update(struct btd_ranging *ranging,
+ uint32_t distance_millimeters);
+
+struct btd_ranging_provider_manager *
+btd_ranging_provider_manager_create(struct btd_adapter *adapter);
+void btd_ranging_provider_manager_destroy(
+ struct btd_ranging_provider_manager *manager);
--
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH BlueZ v3 6/7] client: Add ranging distance display support to bluetoothctl
2026-08-27 5:20 [PATCH BlueZ v3 0/7] Add ranging provider implementation Naga Bhavani Akella
` (4 preceding siblings ...)
2026-08-27 5:20 ` [PATCH BlueZ v3 5/7] src: Add Ranging provider D-Bus API Naga Bhavani Akella
@ 2026-08-27 5:20 ` Naga Bhavani Akella
2026-08-27 5:20 ` [PATCH BlueZ v3 7/7] test: Add Python Ranging Provider example Naga Bhavani Akella
2026-09-02 17:10 ` [PATCH BlueZ v3 0/7] Add ranging provider implementation patchwork-bot+bluetooth
7 siblings, 0 replies; 10+ messages in thread
From: Naga Bhavani Akella @ 2026-08-27 5:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg,
Naga Bhavani Akella
Track org.bluez.Ranging proxies and append/remove them
on InterfacesAdded/InterfacesRemoved.
Print the Distance property on PropertiesChanged,
and show it under info option alongside the existing
Battery Percentage and CS Active properties.
Add register-provider and unregister-provider options
as reference implementation.
---
client/cs.c | 514 +++++++++++++++++++++++++++++++++++++++++++++++++-
client/cs.h | 6 +
client/main.c | 58 ++++++
3 files changed, 577 insertions(+), 1 deletion(-)
diff --git a/client/cs.c b/client/cs.c
index 9f069589d..8c9187678 100644
--- a/client/cs.c
+++ b/client/cs.c
@@ -303,7 +303,7 @@ static const char *cs_resolve_address(const char *address)
}
/* Find the ChannelSounding1 proxy for the device at the given object path. */
-static GDBusProxy *cs_find_proxy(const char *dev_path)
+GDBusProxy *cs_find_proxy(const char *dev_path)
{
GDBusProxy *proxy;
const char *path;
@@ -342,6 +342,487 @@ static struct cs_session *cs_find_session(GDBusProxy *proxy)
return NULL;
}
+/* ---- Ranging provider (reference implementation) ----
+ *
+ * Registers bluetoothctl itself as a Channel Sounding ranging provider
+ * (org.bluez.RangingProviderManager1 / org.bluez.RangingProvider1) purely
+ * to illustrate how a third-party ranging daemon hooks into ProcedureData
+ * and reports a distance; the value computed below is a simple, bounded
+ * placeholder, not a real ranging algorithm. See
+ * test/example-ranging-provider for a standalone daemon built the same way.
+ *
+ * The provider root path defaults to "/" because bluetoothctl only attaches
+ * a D-Bus ObjectManager at "/" (see g_dbus_attach_object_manager() in
+ * main()); bluetoothd's RegisterRangingProvider watches the exact path it
+ * is given via GetManagedObjects()/InterfacesAdded, so any other path would
+ * silently never be discovered.
+ */
+
+#define RANGING_PROVIDER_INTERFACE "org.bluez.RangingProvider1"
+#define CS_PROCEDURE_DATA_INTERFACE "org.bluez.ChannelSounding1"
+#define DEFAULT_PROVIDER_PATH "/"
+
+struct cs_ranging_obj {
+ char *path; /* exported RangingProvider object path */
+ char *dev_path; /* Device this estimate applies to */
+ uint32_t distance_mm;
+ bool has_value;
+};
+
+static DBusConnection *cs_conn;
+static GDBusProxy *cs_ranging_manager;
+static char *cs_provider_path;
+static char *cs_pending_provider_path;
+static guint cs_procedure_data_watch;
+static GList *cs_ranging_objs;
+
+void cs_set_dbus_conn(DBusConnection *conn)
+{
+ cs_conn = conn;
+}
+
+void cs_ranging_manager_added(GDBusProxy *proxy)
+{
+ cs_ranging_manager = proxy;
+}
+
+void cs_ranging_manager_removed(GDBusProxy *proxy)
+{
+ if (cs_ranging_manager == proxy)
+ cs_ranging_manager = NULL;
+}
+
+static struct cs_ranging_obj *cs_find_ranging_obj(const char *dev_path)
+{
+ struct cs_ranging_obj *obj;
+ GList *list;
+
+ for (list = g_list_first(cs_ranging_objs); list;
+ list = g_list_next(list)) {
+ obj = list->data;
+
+ if (!strcmp(obj->dev_path, dev_path))
+ return obj;
+ }
+
+ return NULL;
+}
+
+static gboolean ranging_obj_device_get(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct cs_ranging_obj *obj = data;
+ const char *path = obj->dev_path;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
+
+ return TRUE;
+}
+
+static gboolean ranging_obj_distance_get(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct cs_ranging_obj *obj = data;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32,
+ &obj->distance_mm);
+
+ return TRUE;
+}
+
+static gboolean ranging_obj_distance_exists(const GDBusPropertyTable *property,
+ void *data)
+{
+ struct cs_ranging_obj *obj = data;
+
+ return obj->has_value;
+}
+
+static const GDBusPropertyTable ranging_obj_properties[] = {
+ { "Device", "o", ranging_obj_device_get },
+ { "Distance", "u", ranging_obj_distance_get, NULL,
+ ranging_obj_distance_exists },
+ { }
+};
+
+static void cs_ranging_obj_free(struct cs_ranging_obj *obj)
+{
+ if (cs_conn)
+ g_dbus_unregister_interface(cs_conn, obj->path,
+ RANGING_PROVIDER_INTERFACE);
+
+ g_free(obj->path);
+ g_free(obj->dev_path);
+ g_free(obj);
+}
+
+static void cs_ranging_objs_clear(void)
+{
+ g_list_free_full(cs_ranging_objs, (GDestroyNotify)cs_ranging_obj_free);
+ cs_ranging_objs = NULL;
+}
+
+static struct cs_ranging_obj *cs_ranging_obj_create(const char *dev_path)
+{
+ struct cs_ranging_obj *obj;
+ const char *leaf;
+
+ leaf = strrchr(dev_path, '/');
+ leaf = leaf ? leaf + 1 : dev_path;
+
+ obj = g_new0(struct cs_ranging_obj, 1);
+ obj->dev_path = g_strdup(dev_path);
+
+ if (!strcmp(cs_provider_path, "/"))
+ obj->path = g_strdup_printf("/%s", leaf);
+ else
+ obj->path = g_strdup_printf("%s/%s", cs_provider_path, leaf);
+
+ if (!g_dbus_register_interface(cs_conn, obj->path,
+ RANGING_PROVIDER_INTERFACE, NULL, NULL,
+ ranging_obj_properties, obj, NULL)) {
+ bt_shell_printf("Failed to register %s on %s\n",
+ RANGING_PROVIDER_INTERFACE, obj->path);
+ g_free(obj->dev_path);
+ g_free(obj->path);
+ g_free(obj);
+ return NULL;
+ }
+
+ cs_ranging_objs = g_list_append(cs_ranging_objs, obj);
+
+ return obj;
+}
+
+/* Round-Trip Time distance estimation from Channel Sounding data.
+ *
+ * See doc/org.bluez.ChannelSounding1.rst for the ProcedureData binary
+ * layout parsed below; it mirrors profiles/ranging/rap.c's serializer.
+ */
+
+#define CS_STEP_MODE_ONE 0x01
+#define CS_STEP_MODE_TWO 0x02
+#define CS_STEP_MODE_THREE 0x03
+#define CS_RTT_NOT_AVAILABLE ((int16_t)0x8000)
+#define CS_MAX_ANT_PATHS 5
+
+static bool blob_check(int len, size_t off, size_t need)
+{
+ if (len < 0 || off > (size_t)len)
+ return false;
+
+ return need <= (size_t)len - off;
+}
+
+static bool parse_step_rtt(const uint8_t *data, int len, size_t *off,
+ uint8_t num_ant_paths, int64_t *sum,
+ int *count)
+{
+ uint8_t step_mode;
+ size_t mode_len;
+ int paths;
+
+ if (!blob_check(len, *off, 2))
+ return false;
+
+ step_mode = data[*off];
+ *off += 2;
+
+ paths = num_ant_paths + 1;
+ if (paths > CS_MAX_ANT_PATHS)
+ paths = CS_MAX_ANT_PATHS;
+
+ switch (step_mode) {
+ case CS_STEP_MODE_ONE:
+ mode_len = 16;
+ break;
+ case CS_STEP_MODE_TWO:
+ mode_len = 1 + paths * 5;
+ break;
+ case CS_STEP_MODE_THREE:
+ mode_len = 16 + 1 + paths * 5;
+ break;
+ default:
+ mode_len = 5; /* CS Mode 0 */
+ break;
+ }
+
+ if (!blob_check(len, *off, mode_len))
+ return false;
+
+ if (step_mode == CS_STEP_MODE_ONE || step_mode == CS_STEP_MODE_THREE) {
+ int16_t toa_tod_init = (int16_t)get_le16(data + *off + 3);
+ int16_t tod_toa_refl = (int16_t)get_le16(data + *off + 5);
+
+ if (toa_tod_init != CS_RTT_NOT_AVAILABLE &&
+ tod_toa_refl != CS_RTT_NOT_AVAILABLE) {
+ *sum += (int64_t)toa_tod_init - tod_toa_refl;
+ (*count)++;
+ }
+ } else if (step_mode == CS_STEP_MODE_TWO) {
+ /* Mode 2 (PBR-only) steps carry no RTT timestamps at all, so
+ * this is NOT a real distance measurement -- it's a bounded
+ * random filler value, purely so PBR-only procedures still
+ * report *something*. Mixing it into the same sum/count as
+ * genuine Mode 1/3 RTT samples means the resulting average
+ * is only a true RTT distance when every contributing step
+ * is Mode 1/3; any Mode 2 step in the mix makes the overall
+ * estimate inexact.
+ */
+ *sum += rand() % 100;
+ (*count)++;
+ }
+
+ *off += mode_len;
+
+ return true;
+}
+
+static bool parse_subevent_array_rtt(const uint8_t *data, int len,
+ size_t *off, int64_t *sum, int *count)
+{
+ uint32_t num_subevents;
+ uint32_t i;
+
+ if (!blob_check(len, *off, 4))
+ return false;
+
+ num_subevents = get_le32(data + *off);
+ *off += 4;
+
+ for (i = 0; i < num_subevents; i++) {
+ uint8_t num_ant_paths;
+ uint32_t num_steps;
+ uint32_t j;
+
+ if (!blob_check(len, *off, 19))
+ return false;
+
+ num_ant_paths = data[*off + 5];
+ num_steps = get_le32(data + *off + 15);
+ *off += 19;
+
+ for (j = 0; j < num_steps; j++) {
+ if (!parse_step_rtt(data, len, off, num_ant_paths,
+ sum, count))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* Ticks are 0.5 ns units; one-way distance (mm) = ticks * 0.5e-9 s *
+ * speed_of_light (mm/s) / 2. Kept in fixed-point (1e7 scale) to avoid
+ * pulling in libm for a single rounded multiply.
+ */
+static uint32_t cs_rtt_ticks_to_mm(int64_t sum, int count)
+{
+ int64_t ticks = sum / count;
+
+ if (ticks < 0)
+ ticks = 0;
+
+ return (uint32_t)((ticks * 749481145LL + 5000000LL) / 10000000LL);
+}
+
+static gboolean procedure_data_cb(DBusConnection *connection,
+ DBusMessage *message, void *user_data)
+{
+ const char *dev_path = dbus_message_get_path(message);
+ struct cs_ranging_obj *obj;
+ DBusMessageIter iter, array;
+ uint8_t *data = NULL;
+ int len = 0;
+ int64_t sum = 0;
+ int count = 0;
+
+ if (!dbus_message_iter_init(message, &iter) ||
+ dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
+ return TRUE;
+
+ dbus_message_iter_recurse(&iter, &array);
+ dbus_message_iter_get_fixed_array(&array, &data, &len);
+
+ /* ProcedureData header is procedure_counter (2), procedure_sequence
+ * (2), initiator/reflector selected_tx_power (1 each), followed by
+ * the initiator- and reflector-side subevent arrays (each preceded
+ * by a le32 count and followed by a 1-byte abort reason). Average
+ * every Mode 1/3 RTT sample found on either side; malformed or
+ * truncated data just yields no samples for this procedure.
+ */
+ if (blob_check(len, 6, 0)) {
+ size_t off = 6;
+
+ if (parse_subevent_array_rtt(data, len, &off, &sum, &count) &&
+ blob_check(len, off, 1)) {
+ off += 1;
+ parse_subevent_array_rtt(data, len, &off, &sum,
+ &count);
+ }
+ }
+
+ obj = cs_find_ranging_obj(dev_path);
+ if (!obj) {
+ obj = cs_ranging_obj_create(dev_path);
+ if (!obj)
+ return TRUE;
+ }
+
+ if (!count)
+ return TRUE;
+
+ obj->distance_mm = cs_rtt_ticks_to_mm(sum, count);
+ obj->has_value = true;
+
+ bt_shell_printf("Reference ranging provider: %s Distance %.1f cm"
+ " (RTT estimate from %d CS step%s, %d-byte"
+ " ProcedureData)\n",
+ dev_path, obj->distance_mm / 10.0, count,
+ count == 1 ? "" : "s", len);
+
+ g_dbus_emit_property_changed(cs_conn, obj->path,
+ RANGING_PROVIDER_INTERFACE, "Distance");
+
+ return TRUE;
+}
+
+static void cs_provider_cleanup(void)
+{
+ if (cs_procedure_data_watch) {
+ g_dbus_remove_watch(cs_conn, cs_procedure_data_watch);
+ cs_procedure_data_watch = 0;
+ }
+
+ cs_ranging_objs_clear();
+
+ g_free(cs_provider_path);
+ cs_provider_path = NULL;
+}
+
+static void register_provider_setup(DBusMessageIter *iter, void *user_data)
+{
+ const char *path = cs_pending_provider_path;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
+}
+
+static void register_provider_reply(DBusMessage *message, void *user_data)
+{
+ DBusError error;
+
+ dbus_error_init(&error);
+ if (dbus_set_error_from_message(&error, message)) {
+ bt_shell_printf("RegisterRangingProvider failed: %s\n",
+ error.message);
+ dbus_error_free(&error);
+ g_free(cs_pending_provider_path);
+ cs_pending_provider_path = NULL;
+ return;
+ }
+
+ cs_provider_path = cs_pending_provider_path;
+ cs_pending_provider_path = NULL;
+
+ cs_procedure_data_watch = g_dbus_add_signal_watch(cs_conn, NULL, NULL,
+ CS_PROCEDURE_DATA_INTERFACE,
+ "ProcedureData", procedure_data_cb,
+ NULL, NULL);
+
+ bt_shell_printf("Ranging provider registered at %s (reference"
+ " implementation, see test/example-ranging-provider)\n",
+ cs_provider_path);
+}
+
+static void cmd_cs_register_provider(int argc, char *argv[])
+{
+ if (!cs_conn) {
+ bt_shell_printf("D-Bus connection not ready\n");
+ return;
+ }
+
+ if (!cs_ranging_manager) {
+ bt_shell_printf("No RangingProviderManager interface"
+ " available\n");
+ return;
+ }
+
+ if (cs_provider_path) {
+ bt_shell_printf("Ranging provider already registered at"
+ " %s\n", cs_provider_path);
+ return;
+ }
+
+ if (cs_pending_provider_path) {
+ bt_shell_printf("Ranging provider registration already in"
+ " progress\n");
+ return;
+ }
+
+ cs_pending_provider_path = g_strdup(argc >= 2 ? argv[1] :
+ DEFAULT_PROVIDER_PATH);
+
+ if (strcmp(cs_pending_provider_path, DEFAULT_PROVIDER_PATH))
+ bt_shell_printf("Note: bluetoothctl only exposes an"
+ " ObjectManager at \"/\"; a provider path"
+ " other than \"/\" will register but"
+ " bluetoothd will not discover any"
+ " RangingProvider objects under it\n");
+
+ if (!g_dbus_proxy_method_call(cs_ranging_manager,
+ "RegisterRangingProvider",
+ register_provider_setup,
+ register_provider_reply, NULL, NULL)) {
+ bt_shell_printf("Failed to send RegisterRangingProvider\n");
+ g_free(cs_pending_provider_path);
+ cs_pending_provider_path = NULL;
+ }
+}
+
+static void unregister_provider_setup(DBusMessageIter *iter, void *user_data)
+{
+ const char *path = cs_provider_path;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
+}
+
+static void unregister_provider_reply(DBusMessage *message, void *user_data)
+{
+ DBusError error;
+
+ dbus_error_init(&error);
+ if (dbus_set_error_from_message(&error, message)) {
+ bt_shell_printf("UnregisterRangingProvider failed: %s\n",
+ error.message);
+ dbus_error_free(&error);
+ return;
+ }
+
+ bt_shell_printf("Ranging provider unregistered\n");
+ cs_provider_cleanup();
+}
+
+static void cmd_cs_unregister_provider(int argc, char *argv[])
+{
+ if (!cs_provider_path) {
+ bt_shell_printf("No ranging provider is registered\n");
+ return;
+ }
+
+ if (!cs_ranging_manager) {
+ bt_shell_printf("No RangingProviderManager interface"
+ " available\n");
+ return;
+ }
+
+ if (!g_dbus_proxy_method_call(cs_ranging_manager,
+ "UnregisterRangingProvider",
+ unregister_provider_setup,
+ unregister_provider_reply, NULL,
+ NULL))
+ bt_shell_printf("Failed to send UnregisterRangingProvider\n");
+}
+
/* ---- dict helpers ---- */
static void dict_append_byte(DBusMessageIter *dict, const char *key,
@@ -969,6 +1450,25 @@ static const struct bt_shell_menu cs_menu = {
cmd_cs_show,
"Show active session id and current"
" CS parameters" },
+ { "register_provider", "[path]",
+ cmd_cs_register_provider,
+ "Reference implementation only: registers"
+ " bluetoothctl itself as a Channel Sounding"
+ " ranging provider (RegisterRangingProvider),\n"
+ "\t\t\t\t\t\tthen turns each ProcedureData signal into"
+ " an illustrative, non-accurate distance"
+ " exposed via RangingProvider/Ranging.\n"
+ "\t\t\t\t\t\tpath shall be /org/bluez/hci0/dev_XX\n"
+ "\t\t\t\t\t\tsee test/example-ranging-provider for a"
+ " realistic standalone daemon.\n\t\t\t\t\t\tOnly one"
+ " provider may be registered per adapter at"
+ " a time; fails with AlreadyExists if a real"
+ " ranging daemon is already registered, and"
+ " vice versa."},
+ { "unregister_provider", NULL,
+ cmd_cs_unregister_provider,
+ "Unregister the ranging provider registered"
+ " via register_provider" },
{ "role", "<0x01|0x02|0x03>", cmd_cs_set,
"CS role: 0x01 Initiator, 0x02 Reflector,"
" 0x03 Both (default 0x03)",
@@ -1075,10 +1575,22 @@ void cs_add_submenu(void)
}
void cs_remove_submenu(void)
+{
+ cs_disconnected();
+}
+
+void cs_disconnected(void)
{
g_list_free_full(cs_sessions, g_free);
cs_sessions = NULL;
g_list_free(cs_proxies);
cs_proxies = NULL;
+
+ cs_ranging_manager = NULL;
+
+ g_free(cs_pending_provider_path);
+ cs_pending_provider_path = NULL;
+
+ cs_provider_cleanup();
}
diff --git a/client/cs.h b/client/cs.h
index 243d2a72b..10e7bbeeb 100644
--- a/client/cs.h
+++ b/client/cs.h
@@ -9,10 +9,16 @@
void cs_add_submenu(void);
void cs_remove_submenu(void);
+void cs_disconnected(void);
void cs_proxy_added(GDBusProxy *proxy);
void cs_proxy_removed(GDBusProxy *proxy);
+GDBusProxy *cs_find_proxy(const char *dev_path);
void cs_device_disconnected(const char *dev_path);
void cs_measurement_started(GDBusProxy *proxy);
void cs_measurement_stopped(GDBusProxy *proxy);
void cs_set_device_list(GList **devices);
+
+void cs_ranging_manager_added(GDBusProxy *proxy);
+void cs_ranging_manager_removed(GDBusProxy *proxy);
+void cs_set_dbus_conn(DBusConnection *conn);
diff --git a/client/main.c b/client/main.c
index 069e20485..c4d9a4022 100644
--- a/client/main.c
+++ b/client/main.c
@@ -69,6 +69,7 @@ static char *default_local_attr;
static GDBusProxy *default_attr;
static GList *ctrl_list;
static GList *battery_proxies;
+static GList *ranging_proxies;
static const char *agent_arguments[] = {
"on",
@@ -121,11 +122,14 @@ static void disconnect_handler(DBusConnection *connection, void *user_data)
g_list_free_full(ctrl_list, proxy_leak);
g_list_free_full(battery_proxies, proxy_leak);
+ g_list_free_full(ranging_proxies, proxy_leak);
ctrl_list = NULL;
battery_proxies = NULL;
+ ranging_proxies = NULL;
default_ctrl = NULL;
cs_set_device_list(NULL);
+ cs_disconnected();
}
static void print_adapter(GDBusProxy *proxy, const char *description)
@@ -358,6 +362,30 @@ static void battery_removed(GDBusProxy *proxy)
battery_proxies = g_list_remove(battery_proxies, proxy);
}
+static void ranging_added(GDBusProxy *proxy)
+{
+ ranging_proxies = g_list_append(ranging_proxies, proxy);
+}
+
+static void ranging_removed(GDBusProxy *proxy)
+{
+ ranging_proxies = g_list_remove(ranging_proxies, proxy);
+}
+
+static void print_distance_cm(GDBusProxy *proxy, const char *label)
+{
+ DBusMessageIter iter;
+ dbus_uint32_t mm;
+
+ if (g_dbus_proxy_get_property(proxy, "Distance",
+ &iter) == FALSE)
+ return;
+
+ dbus_message_iter_get_basic(&iter, &mm);
+
+ bt_shell_printf("\t%s: %.1f cm\n", label, mm / 10.0);
+}
+
static void device_added(GDBusProxy *proxy)
{
DBusMessageIter iter;
@@ -531,6 +559,12 @@ static void proxy_added(GDBusProxy *proxy, void *user_data)
bearer_added(proxy);
} else if (!strcmp(interface, "org.bluez.ChannelSounding1")) {
cs_proxy_added(proxy);
+ } else if (!strcmp(interface, "org.bluez.Ranging1")) {
+ ranging_added(proxy);
+ } else if (!strcmp(interface, "org.bluez.RangingProviderManager1")) {
+ if (default_ctrl && !strcmp(g_dbus_proxy_get_path(proxy),
+ g_dbus_proxy_get_path(default_ctrl->proxy)))
+ cs_ranging_manager_added(proxy);
}
}
@@ -664,6 +698,10 @@ static void proxy_removed(GDBusProxy *proxy, void *user_data)
bearer_removed(proxy);
} else if (!strcmp(interface, "org.bluez.ChannelSounding1")) {
cs_proxy_removed(proxy);
+ } else if (!strcmp(interface, "org.bluez.Ranging1")) {
+ ranging_removed(proxy);
+ } else if (!strcmp(interface, "org.bluez.RangingProviderManager1")) {
+ cs_ranging_manager_removed(proxy);
}
}
@@ -843,6 +881,13 @@ static void property_changed(GDBusProxy *proxy, const char *name,
else
cs_measurement_stopped(proxy);
}
+ } else if (!strcmp(interface, "org.bluez.Ranging1")) {
+ if (!strcmp(name, "Distance")) {
+ const char *path = g_dbus_proxy_get_path(proxy);
+ const char *leaf = strrchr(path, '/');
+
+ print_distance_cm(proxy, leaf ? leaf + 1 : path);
+ }
}
}
@@ -1882,6 +1927,8 @@ static void cmd_info(int argc, char *argv[])
{
GDBusProxy *proxy;
GDBusProxy *battery_proxy;
+ GDBusProxy *cs_proxy;
+ GDBusProxy *ranging_proxy;
GDBusProxy *bearer;
DBusMessageIter iter;
const char *address;
@@ -1934,6 +1981,16 @@ static void cmd_info(int argc, char *argv[])
print_property_with_label(battery_proxy, "Percentage",
"Battery Percentage");
+ cs_proxy = cs_find_proxy(g_dbus_proxy_get_path(proxy));
+ if (cs_proxy)
+ print_property_with_label(cs_proxy, "Active",
+ "CS Session Active");
+
+ ranging_proxy = find_proxies_by_path(ranging_proxies,
+ g_dbus_proxy_get_path(proxy));
+ if (ranging_proxy)
+ print_distance_cm(ranging_proxy, "Ranging Distance");
+
bearer = find_proxies_by_iface(default_ctrl->bearers,
g_dbus_proxy_get_path(proxy),
"org.bluez.Bearer.BREDR1");
@@ -4050,6 +4107,7 @@ int main(int argc, char *argv[])
dbus_conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL);
g_dbus_attach_object_manager(dbus_conn);
+ cs_set_dbus_conn(dbus_conn);
bt_shell_set_env("DBUS_CONNECTION", dbus_conn);
--
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH BlueZ v3 7/7] test: Add Python Ranging Provider example
2026-08-27 5:20 [PATCH BlueZ v3 0/7] Add ranging provider implementation Naga Bhavani Akella
` (5 preceding siblings ...)
2026-08-27 5:20 ` [PATCH BlueZ v3 6/7] client: Add ranging distance display support to bluetoothctl Naga Bhavani Akella
@ 2026-08-27 5:20 ` Naga Bhavani Akella
2026-09-02 17:10 ` [PATCH BlueZ v3 0/7] Add ranging provider implementation patchwork-bot+bluetooth
7 siblings, 0 replies; 10+ messages in thread
From: Naga Bhavani Akella @ 2026-08-27 5:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg,
Naga Bhavani Akella
This patch introduces test/example-ranging-provider which implements
a simple D-Bus client application for ranging profile.
---
Makefile.tools | 1 +
test/example-ranging-provider | 222 ++++++++++++++++++++++++++++++++++
2 files changed, 223 insertions(+)
create mode 100644 test/example-ranging-provider
diff --git a/Makefile.tools b/Makefile.tools
index b3ef4ae1c..4a59b6dee 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -559,6 +559,7 @@ test_scripts += test/bluezutils.py \
test/test-hfp test/opp-client test/ftp-client \
test/pbap-client test/map-client test/example-advertisement \
test/example-gatt-server test/example-gatt-client \
+ test/example-ranging-provider \
test/test-gatt-profile test/test-mesh test/agent.py
if BTPCLIENT
diff --git a/test/example-ranging-provider b/test/example-ranging-provider
new file mode 100644
index 000000000..72edfca6f
--- /dev/null
+++ b/test/example-ranging-provider
@@ -0,0 +1,222 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+import dbus
+import dbus.exceptions
+import dbus.mainloop.glib
+import dbus.service
+
+try:
+ from gi.repository import GObject
+except ImportError:
+ import gobject as GObject
+
+mainloop = None
+app = None
+bus = None
+
+BLUEZ_SERVICE_NAME = 'org.bluez'
+DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager'
+DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
+
+RANGING_PROVIDER_MANAGER_IFACE = 'org.bluez.RangingProviderManager1'
+RANGING_PROVIDER_IFACE = 'org.bluez.RangingProvider1'
+RANGING_PROVIDER_PATH = '/org/bluez/hci0'
+
+CS_IFACE = 'org.bluez.ChannelSounding1'
+
+
+class InvalidArgsException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.freedesktop.DBus.Error.InvalidArgs'
+
+
+class Application(dbus.service.Object):
+ def __init__(self, bus):
+ self.path = RANGING_PROVIDER_PATH
+ self.rangings = {}
+ dbus.service.Object.__init__(self, bus, self.path)
+
+ def get_path(self):
+ return dbus.ObjectPath(self.path)
+
+ def add_ranging(self, ranging):
+ self.rangings[ranging.dev_path] = ranging
+ self.InterfacesAdded(ranging.get_path(), ranging.get_properties())
+
+ def remove_ranging(self, ranging):
+ del self.rangings[ranging.dev_path]
+ self.InterfacesRemoved(ranging.get_path(), [RANGING_PROVIDER_IFACE])
+
+ def find_by_device(self, dev_path):
+ return self.rangings.get(dev_path)
+
+ @dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}')
+ def GetManagedObjects(self):
+ response = {}
+
+ for ranging in self.rangings.values():
+ response[ranging.get_path()] = ranging.get_properties()
+
+ return response
+
+ @dbus.service.signal(DBUS_OM_IFACE, signature='oa{sa{sv}}')
+ def InterfacesAdded(self, object_path, interfaces_and_properties):
+ return
+
+ @dbus.service.signal(DBUS_OM_IFACE, signature='oas')
+ def InterfacesRemoved(self, object_path, interfaces):
+ return
+
+
+class Ranging(dbus.service.Object):
+ """
+ org.bluez.RangingProvider1 interface implementation
+ """
+ def __init__(self, bus, dev_path):
+ dev = dev_path.rsplit('/', 1)[-1]
+ self.path = RANGING_PROVIDER_PATH + '/' + dev
+ self.dev_path = dev_path
+ self.bus = bus
+ self.millimeters = None
+ dbus.service.Object.__init__(self, bus, self.path)
+
+ def get_ranging_properties(self):
+ properties = {}
+ if self.millimeters is not None:
+ properties['Distance'] = dbus.UInt32(self.millimeters)
+ properties['Device'] = dbus.ObjectPath(self.dev_path)
+ return properties
+
+ def get_properties(self):
+ return { RANGING_PROVIDER_IFACE: self.get_ranging_properties() }
+
+ def get_path(self):
+ return dbus.ObjectPath(self.path)
+
+ def set_distance_millimeters(self, millimeters):
+ self.millimeters = millimeters
+ print('ranging %s Distance %d' %
+ (self.path, self.millimeters))
+ self.PropertiesChanged(
+ RANGING_PROVIDER_IFACE, self.get_ranging_properties(), [])
+
+ @dbus.service.method(DBUS_PROP_IFACE,
+ in_signature='s',
+ out_signature='a{sv}')
+ def GetAll(self, interface):
+ if interface != RANGING_PROVIDER_IFACE:
+ raise InvalidArgsException()
+
+ return self.get_properties()[RANGING_PROVIDER_IFACE]
+
+ @dbus.service.signal(DBUS_PROP_IFACE, signature='sa{sv}as')
+ def PropertiesChanged(self, interface, properties, invalidated):
+ return
+
+
+def procedure_data_cb(data, path, interface):
+ ranging = app.find_by_device(path)
+ if ranging is None:
+ ranging = Ranging(bus, path)
+ app.add_ranging(ranging)
+
+ # This example does not parse ProcedureData at all; it just reports a
+ # fixed 2 meters on every procedure to illustrate the RangingProvider
+ # wiring.
+ ranging.set_distance_millimeters(2000)
+
+
+def register_provider_cb():
+ print('Ranging Provider registered')
+
+
+def register_provider_error_cb(error):
+ print('Failed to register Ranging Provider: ' + str(error))
+ mainloop.quit()
+
+
+def find_manager(bus):
+ try:
+ remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'),
+ DBUS_OM_IFACE)
+ objects = remote_om.GetManagedObjects()
+ except dbus.exceptions.DBusException as e:
+ print('Failed to reach bluetoothd: ' + str(e))
+ return None
+
+ for o, props in objects.items():
+ if RANGING_PROVIDER_MANAGER_IFACE in props.keys():
+ return o
+
+ return None
+
+
+def unregister_provider_cb():
+ print('Ranging Provider unregistered')
+ for ranging in list(app.rangings.values()):
+ app.remove_ranging(ranging)
+ mainloop.quit()
+
+
+def unregister_provider_error_cb(error):
+ print('Failed to unregister Ranging Provider: ' + str(error))
+
+
+def unregister_ranging_provider(ranging_provider_manager):
+ ranging_provider_manager.UnregisterRangingProvider(RANGING_PROVIDER_PATH,
+ reply_handler=unregister_provider_cb,
+ error_handler=unregister_provider_error_cb)
+
+
+def main():
+ """
+ Simulates an external Channel Sounding ranging daemon: it registers with
+ BlueZ as a Ranging Provider, then listens for ProcedureData signals from
+ every device exposing org.bluez.ChannelSounding1 and turns each completed
+ procedure into a Distance update on the matching
+ RangingProvider object.
+ """
+ global mainloop, bus, app
+
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ try:
+ bus = dbus.SystemBus()
+ except dbus.exceptions.DBusException as e:
+ print('Failed to connect to the system bus: ' + str(e))
+ return
+
+ manager_path = find_manager(bus)
+ if not manager_path:
+ print('RangingProviderManager interface not found')
+ return
+
+ print('RangingProviderManager path = ', manager_path)
+
+ ranging_provider_manager = dbus.Interface(
+ bus.get_object(BLUEZ_SERVICE_NAME, manager_path),
+ RANGING_PROVIDER_MANAGER_IFACE)
+
+ app = Application(bus)
+
+ bus.add_signal_receiver(procedure_data_cb, signal_name='ProcedureData',
+ dbus_interface=CS_IFACE, path_keyword='path',
+ interface_keyword='interface')
+
+ mainloop = GObject.MainLoop()
+
+ print('Registering Ranging Provider...')
+
+ ranging_provider_manager.RegisterRangingProvider(RANGING_PROVIDER_PATH,
+ reply_handler=register_provider_cb,
+ error_handler=register_provider_error_cb)
+
+ # Unregister the Ranging Provider after an arbitrary amount of time
+ GObject.timeout_add(
+ 60000, unregister_ranging_provider, ranging_provider_manager)
+
+ mainloop.run()
+
+
+if __name__ == '__main__':
+ main()
--
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH BlueZ v3 0/7] Add ranging provider implementation
2026-08-27 5:20 [PATCH BlueZ v3 0/7] Add ranging provider implementation Naga Bhavani Akella
` (6 preceding siblings ...)
2026-08-27 5:20 ` [PATCH BlueZ v3 7/7] test: Add Python Ranging Provider example Naga Bhavani Akella
@ 2026-09-02 17:10 ` patchwork-bot+bluetooth
7 siblings, 0 replies; 10+ messages in thread
From: patchwork-bot+bluetooth @ 2026-09-02 17:10 UTC (permalink / raw)
To: Naga Bhavani Akella
Cc: linux-bluetooth, luiz.dentz, quic_mohamull, quic_hbandi,
quic_anubhavg, nakella
Hello:
This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Thu, 27 Aug 2026 10:50:27 +0530 you wrote:
> From: Naga Bhavani Akella <nakella@qti.qualcomm.com>
>
> This series documents and implements the D-Bus API that lets an
> external ranging daemon compute Channel Sounding distance estimates
> from the raw procedure data BlueZ reports, and feed them back for
> reflection on the device's Channel Sounding interface
>
> [...]
Here is the summary with links:
- [BlueZ,v3,1/7] doc: Add org.bluez.Ranging1 documentation
(no matching commit)
- [BlueZ,v3,2/7] doc: Add org.bluez.RangingProvider1 documentation
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=0654aa95ce08
- [BlueZ,v3,3/7] doc: Add org.bluez.RangingProviderManager1 documentation
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=da77259717e4
- [BlueZ,v3,4/7] doc: Modify bluetoothctl-cs documentation
(no matching commit)
- [BlueZ,v3,5/7] src: Add Ranging provider D-Bus API
(no matching commit)
- [BlueZ,v3,6/7] client: Add ranging distance display support to bluetoothctl
(no matching commit)
- [BlueZ,v3,7/7] test: Add Python Ranging Provider example
(no matching commit)
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 10+ messages in thread