Register a Bluetooth agent with Python DBus to hci1 (not hci0)

1.9k Views Asked by At

I have been using copy, paste, and magic to register a Bluetooth agent via Python DBUs which works great for hci0, but I cannot for the life of me see how I can get this agent to work for other Bluetooth controllers, i.e hci1. I have tried selecting the controller and setting it as default in bluetoothctl and other side channels.

Can someone please show me where the agent is associated with the controller. This is all too magical. I am also unable to find the agent or anything on it with D-Feet - How should or could I find it please?

A dumb toy example is as:


import dbus
import dbus.service

AGENT_PATH = "/org/bluez/anAgent"

class Agent(dbus.service.Object):
  
    @dbus.service.method(AGENT_INTERFACE,
                    in_signature="os", out_signature="")
    def AuthorizeService(self, device, uuid):
      print("Some stuff and things")
      
if __name__ == '__main__':
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)

    bus = dbus.SystemBus()

    capability = "DisplayYesNo"

    agent = Agent(bus, AGENT_PATH)

    obj = bus.get_object("org.bluez", "/org/bluez")

    # Create the agent manager
    manager = dbus.Interface(obj, "org.bluez.AgentManager1")
    manager.RegisterAgent(AGENT_PATH, capability)
    manager.RequestDefaultAgent(AGENT_PATH)

1

There are 1 best solutions below

4
On

When Bluetooth needs an agent it looks at the org.bluez.AgentManager1 interface at the /org/bluez D-Bus object path for the location of the agent that has been registered. It then calls the relevant method on the org.bluez.Agent1 interface at that registered object path (/org/bluez/anAgent in your example). The methods that need to be implemented for the org.bluez.Agent1 interface are documented at: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/agent-api.txt

There is only one agent irrespective of how many adapters there are on your system.

Could the issue be that the device being paired is associated with the "wrong" adapter?

The Device API has an Adapter property which tracks the object path of the adapter the device belongs to.

To find the agent that you have created it can be useful to use busctl. Using busctl list will list all the available SystemBus services. As you are not claiming a bus name for the agent it will be anonymous so in the format of :x.xxx. This can be a long list so busctl list | grep python is what I usually do to narrow the list down.

To introspect what was there you would do something like:

$ busctl introspect :1.174 /org/bluez/anAgent

To test the agent service that has been published:

$ busctl call :1.174 /org/bluez/anAgent org.bluez.Agent1 AuthorizeService os /org/bluez/hci1/dev_12_34_56_78 1234

This service will need to be asynchronous so you will need to start the MainLoop event loop.

Below is a full example of creating an Agent using pydbus D-Bus bindings.

The only place that the adapter is mentioned is the instruction to start discovery. If you had another device trying to discover your device with the agent, then it will depend on which adapter is discoverable and/or the other device tries to connect to.

import threading
import pydbus
from gi.repository import GLib

BUS_NAME = 'org.bluez'
AGENT_IFACE = 'org.bluez.Agent1'
AGNT_MNGR_IFACE = 'org.bluez.AgentManager1'
ADAPTER_IFACE = 'org.bluez.Adapter1'
AGENT_PATH = '/org/bluez/anAgent'
AGNT_MNGR_PATH = '/org/bluez'
DEVICE_IFACE = 'org.bluez.Device1'
CAPABILITY = 'KeyboardDisplay'
bus = pydbus.SystemBus()


class Agent:
    """
    <node>
        <interface name="org.bluez.Agent1">
            <method name="Release" />
            <method name="RequestPinCode">
                <arg name="device" direction="in" type="o" />
                <arg name="pincode" direction="out" type="s" />
            </method>
            <method name="DisplayPinCode">
                <arg name="device" direction="in" type="o" />
                <arg name="pincode" direction="in" type="s" />
            </method>
            <method name="RequestPasskey">
                <arg name="device" direction="in" type="o" />
                <arg name="passkey" direction="out" type="u" />
            </method>
            <method name="DisplayPasskey">
                <arg name="device" direction="in" type="o" />
                <arg name="passkey" direction="in" type="u" />
                <arg name="entered" direction="in" type="q" />
            </method>
            <method name="RequestConfirmation">
                <arg name="device" direction="in" type="o" />
                <arg name="passkey" direction="in" type="u" />
            </method>
            <method name="RequestAuthorization">
              <arg name="device" direction="in" type="o" />
            </method>
            <method name="AuthorizeService">
                <arg name="device" direction="in" type="o" />
                <arg name="uuid" direction="in" type="s" />
            </method>
            <method name="Cancel" />
        </interface>
    </node>
    """

    def Release(self):
        print('Release')

    def RequestPinCode(self, device):
        print('RequestPinCode', device)
        return '1234'

    def DisplayPinCode(self, device, pincode):
        print('DisplayPinCode', device, pincode)

    def RequestPasskey(self, device):
        print('RequestPasskey', device)
        return 1234

    def DisplayPasskey(self, device, passkey, entered):
        print('DisplayPasskey', device, passkey, entered)

    def RequestConfirmation(self, device, passkey):
        print('RequestConfirmation', device, passkey)

    def RequestAuthorization(self, device):
        print('RequestAuthorization', device)

    def AuthorizeService(self, device, uuid):
        print('AuthorizeService', device, uuid)

    def Cancel(self):
        return


def pair_reply(*args):
    print('reply', args)


def pair_error(*args):
    print('error', args)


def dbus_path_up(dbus_obj):
    return '/'.join(dbus_obj.split('/')[:-1])


def device_found(dbus_obj, properties):
    adapter = bus.get(BUS_NAME, dbus_path_up(dbus_obj))
    device = bus.get(BUS_NAME, dbus_obj)
    print('Stopping discovery')
    adapter.StopDiscovery()
    if device.Paired:
        device.Connect()
    else:
        print('Pairing procedure starting...')
        device.Pair()


def interface_added(path, ifaces):
    if DEVICE_IFACE in ifaces.keys():
        dev_name = ifaces[DEVICE_IFACE].get('Name')
        print('Device found:', dev_name)
        if dev_name == 'HC-06':
            device_found(path, ifaces[DEVICE_IFACE])


def publish_agent():
    bus.register_object(AGENT_PATH, Agent(), None)
    aloop = GLib.MainLoop()
    aloop.run()
    print('Agent Registered')


def create_agent():
    thread = threading.Thread(target=publish_agent, daemon=True)
    thread.start()
    print('Agent running...')


def my_app(hci_idx=0):
    adapter_path = f'/org/bluez/hci{hci_idx}'
    mngr = bus.get(BUS_NAME, '/')
    mngr.onInterfacesAdded = interface_added

    create_agent()
    agnt_mngr = bus.get(BUS_NAME, AGNT_MNGR_PATH)[AGNT_MNGR_IFACE]
    agnt_mngr.RegisterAgent(AGENT_PATH, CAPABILITY)
    print('Agent registered...')
    adapter = bus.get(BUS_NAME, adapter_path)
    # adapter.StartDiscovery()
    mainloop = GLib.MainLoop()
    try:
        mainloop.run()
    except KeyboardInterrupt:
        mainloop.quit()
        adapter.StopDiscovery()


if __name__ == '__main__':
    my_app()