Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH BlueZ v3 0/7]  Add ranging provider implementation
@ 2026-08-27  5:20 Naga Bhavani Akella
  2026-08-27  5:20 ` [PATCH BlueZ v3 1/7] doc: Add org.bluez.Ranging1 documentation Naga Bhavani Akella
                   ` (7 more replies)
  0 siblings, 8 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

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

Patch 1 documents Ranging, the BlueZ-owned interface
that reflects the provider's Distance estimate for consumption
by desktop clients.

Patch 2 documents RangingProvider, the interface the
registered provider implements to expose per-device distance
estimates.

Patch 3 documents RangingProviderManager, which a
ranging daemon uses to register/unregister itself as a distance
provider.

Patch 4 documents addition of new options (register/unregister)
in bluetoothctl cs menu

Patch 5 and 6 implements Ranging Provider and Rangign Provider Manager
interfaces

Patch 7 implements test/example-ranging-provider,
a simple D-Bus client application for ranging profile

Changes in v3:
 - Change interface naming fro CS to ranging
 - Add Register/Unregister provider options as part of CS menu
 - Add example-ranging-provider to show client application for
 ranging profile

Changes in v2:
 - Add implementation of ranging provider and ranging provider manager

Naga Bhavani Akella (7):
  doc: Add org.bluez.Ranging1 documentation
  doc: Add org.bluez.RangingProvider1 documentation
  doc: Add org.bluez.RangingProviderManager1 documentation
  doc: Modify bluetoothctl-cs documentation
  src: Add Ranging provider D-Bus API
  client: Add ranging distance display support to bluetoothctl
  test: Add Python Ranging Provider example

 Makefile.am                               |  16 +-
 Makefile.tools                            |   1 +
 client/cs.c                               | 514 ++++++++++++++++-
 client/cs.h                               |   6 +
 client/main.c                             |  58 ++
 doc/bluetoothctl-cs.rst                   |  41 ++
 doc/org.bluez.Ranging1.rst                |  78 +++
 doc/org.bluez.RangingProvider1.rst        |  92 ++++
 doc/org.bluez.RangingProviderManager1.rst |  91 +++
 src/adapter.c                             |  11 +
 src/ranging.c                             | 644 ++++++++++++++++++++++
 src/ranging.h                             |  25 +
 test/example-ranging-provider             | 222 ++++++++
 13 files changed, 1796 insertions(+), 3 deletions(-)
 create mode 100644 doc/org.bluez.Ranging1.rst
 create mode 100644 doc/org.bluez.RangingProvider1.rst
 create mode 100644 doc/org.bluez.RangingProviderManager1.rst
 create mode 100644 src/ranging.c
 create mode 100644 src/ranging.h
 create mode 100644 test/example-ranging-provider

-- 

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [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: Add ranging provider implementation
  2026-08-27  5:20 ` [PATCH BlueZ v3 1/7] doc: Add org.bluez.Ranging1 documentation Naga Bhavani Akella
@ 2026-08-27 16:16   ` bluez.test.bot
  0 siblings, 0 replies; 10+ messages in thread
From: bluez.test.bot @ 2026-08-27 16:16 UTC (permalink / raw)
  To: linux-bluetooth, naga.akella

[-- Attachment #1: Type: text/plain, Size: 988 bytes --]

This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1152414

---Test result---

Test Summary:
CheckPatch                    PASS      4.51 seconds
GitLint                       PASS      2.33 seconds
BuildEll                      PASS      20.61 seconds
BluezMake                     PASS      559.11 seconds
MakeCheck                     PASS      19.33 seconds
MakeDistcheck                 PASS      157.27 seconds
CheckValgrind                 PASS      224.20 seconds
CheckSmatch                   PASS      297.96 seconds
bluezmakeextell               PASS      96.31 seconds
IncrementalBuild              PASS      631.37 seconds
ScanBuild                     PASS      881.73 seconds



https://github.com/bluez/bluez/pull/2439

---
Regards,
Linux Bluetooth


^ permalink raw reply	[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

end of thread, other threads:[~2026-09-02 17:11 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 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
2026-08-27  5:20 ` [PATCH BlueZ v3 3/7] doc: Add org.bluez.RangingProviderManager1 documentation Naga Bhavani Akella
2026-08-27  5:20 ` [PATCH BlueZ v3 4/7] doc: Modify bluetoothctl-cs documentation Naga Bhavani Akella
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 ` [PATCH BlueZ v3 6/7] client: Add ranging distance display support to bluetoothctl 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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox