0

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)

SpmP
  • 527
  • 1
  • 6
  • 16

1 Answers1

0

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()
ukBaz
  • 6,985
  • 2
  • 8
  • 31
  • Thank you @ukBaz, in my testing this is not what I experienced. The registered agent only works with `hci0`, and any subsequent adapter (whether default or not) uses the standard agent - typically password. What I can see is that the registered agent looks like it tries to handle the pairing/connection, but invokes (missing) password methods. i.e the _capability_ and indeed functionality of the registered agent are ignored, which leaves us back at _why does this work for hci0 and not hci0+n_. I have tested this on a machine that has no prior config. – SpmP Jan 30 '22 at 10:03
  • I would suggest using `sudo btmon` to get a more detailed log of the Bluetooth activity on the system. The log can be read by `wireshark` to help with interpreting the output. D-Bus traffic can be monitored with `sudo busctl monitor org.bluez` – ukBaz Jan 30 '22 at 11:05
  • Thanks for all this @ukBaz! I had to create a very permissive policy file at `/etc/dbus-1/system.d/allow-all.conf` in order to be able to introspect the anonymous bus's. What I am noticing is whether there is an optional agent running or not the same method is being used to pair with each device. `hci0` (built in) always shows a number to validate, whereas `hci1` goes for entering a pin code. I cannot see in `bluetoothctl` where this behaviour could be set. I would have thought it was from the _capabilities_ of the agent. I cannot see dbus calls in `btmon` output, probably my bad. – SpmP Feb 01 '22 at 21:53
  • OK, so it's stranger - when trying to connect to the USB Bluetooth dongle from my phone I get a pin code popup (usually 0000 or 1234 etc.), but `busctl monitor org.bluez` shows nothing even though the phone says the connection was rejected. Monitoring with `busctl` I could finally see pairing request being sent to different connections 8) So is this the phone interpreting the dongle as the kind of device that must be paired to with a pin code? If so, how can that be changed? Is that a MAC spoofing or similar? – SpmP Feb 03 '22 at 06:39