bluetoothdbusbluez

Can GetManagedObjects be used to return a subtree?


In a Python app that accesses Bluetooth, I can query all of the attributes belonging to a connected device with:

import dbus

bus = dbus.SystemBus()

object_manager = dbus.Interface(
    bus.get_object("org.bluez", "/"),
    "org.freedesktop.DBus.ObjectManager")
managed_objects = object_manager.GetManagedObjects()

This works, but it returns all of the managed objects owned by BlueZ, including many that I don't care about. So if I want to limit it to objects that belong to a specific device (e.g. /org/bluez/hci0/dev_00_11_22_33_44_55), I need to explicitly filter the output:

for path, ifaces in managed_objects.items():
    if path.startswith(device_path):
        ....

Is there a better way to do this? Ideally, I'd like to pass my device path to the object manager and only receive paths that are below it, but the GetManagedObjects method doesn't accept any parameters and I don't see any other way to get the necessary information.

I am aware of the fact that I can register a receiver for the InterfacesAdded signal, which works great, but only if the BlueZ queries the remote device. If the data is already cached, I need to explicitly read it from the ObjectManager.


Solution

  • You can get all the object paths by making recursive calls to the Introspect method in the org.freedesktop.DBus.Properties interface, using the device object path as a starting point.

    The resulting introspection data are in XML format. The root introspection element is always a node element. It might have a name attribute, which denotes the (absolute) object path an interface is introspected. It also contains all interfaces, signals, methods exposed in that object path, as well as children object paths (nodes).

    Using the interface is easy, you just need to define it and call the method then parse the XML response:

    introspect_interface = dbus.Interface(
        bus.get_object("org.bluez", object_path),
        "org.freedesktop.DBus.Introspectable"
    )
    xml_data = introspect_interface.Introspect()
    parsed_data = ET.fromstring(xml_data)
    nodes = [node.get('name') for node in parsed_data.findall('node')]
    interfaces = [iface.get('name') for iface in parsed_data.findall('interface')]
    

    Next step would be to call the GetAll() method in the org.freedesktop.DBus.Properties interface for that object path, this method needs one string parameter, this is the interface name and we get it for all the interfaces on that object path, excluding org.freedesktop.DBus.Introspectable, org.freedesktop.DBus.Properties interfaces. This can be done:

    properties_interface = dbus.Interface(
        bus.get_object("org.bluez", object_path),
        "org.freedesktop.DBus.Properties"
    )
    properties = properties_interface.GetAll(interface)
    

    Putting things together, final code would be:

    import dbus
    import xml.etree.ElementTree as ET
    
    common_interfaces = ["org.freedesktop.DBus.Properties", "org.freedesktop.DBus.Introspectable"]
    
    bus = dbus.SystemBus()
    
    def introspect_object_path(object_path):
        print(f"Introspecting:  {object_path}")
        introspect_interface = dbus.Interface(
            bus.get_object("org.bluez", object_path),
            "org.freedesktop.DBus.Introspectable"
        )
        properties_interface = dbus.Interface(
            bus.get_object("org.bluez", object_path),
            "org.freedesktop.DBus.Properties"
        )
        xml_data = introspect_interface.Introspect()
        parsed_data = ET.fromstring(xml_data)
        nodes = [node.get('name') for node in parsed_data.findall('node')]
        interfaces = [iface.get('name') for iface in parsed_data.findall('interface')]
        for interface in interfaces:
            if interface in common_interfaces:
                continue
            print(f"Getting properties for interface: {interface}")
            properties = properties_interface.GetAll(interface)
            # Handle properties...
            print(properties)
        for node in nodes:
            introspect_object_path(object_path + f"/{node}")
    
    
    base_object_path = "/org/bluez/hci0/dev_00_11_22_33_44_55"
    introspect_object_path(base_object_path)