Linux bluetooth development
 help / color / mirror / Atom feed
From: Naga Bhavani Akella <naga.akella@oss.qualcomm.com>
To: linux-bluetooth@vger.kernel.org
Cc: luiz.dentz@gmail.com, quic_mohamull@quicinc.com,
	quic_hbandi@quicinc.com, quic_anubhavg@quicinc.com,
	Naga Bhavani Akella <naga.akella@oss.qualcomm.com>
Subject: [PATCH BlueZ v3 7/7] test: Add Python Ranging Provider example
Date: Thu, 27 Aug 2026 10:50:34 +0530	[thread overview]
Message-ID: <20260827052034.1374180-8-naga.akella@oss.qualcomm.com> (raw)
In-Reply-To: <20260827052034.1374180-1-naga.akella@oss.qualcomm.com>

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()
-- 


  parent reply	other threads:[~2026-08-27  5:21 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 ` Naga Bhavani Akella [this message]
2026-09-02 17:10 ` [PATCH BlueZ v3 0/7] Add ranging provider implementation patchwork-bot+bluetooth

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260827052034.1374180-8-naga.akella@oss.qualcomm.com \
    --to=naga.akella@oss.qualcomm.com \
    --cc=linux-bluetooth@vger.kernel.org \
    --cc=luiz.dentz@gmail.com \
    --cc=quic_anubhavg@quicinc.com \
    --cc=quic_hbandi@quicinc.com \
    --cc=quic_mohamull@quicinc.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox