Search code examples
pythonlinuxdbusbluezrfcomm

How to connect to a bluetooth profile using dbus APIs


I have a python3 script that successfully opens a RFCOMM socket to a server using old-style bluetooth. I'm trying to accomplish the same thing using dbus, which is the way I'm reading you're supposed to use bluetooth on Linux these days. (This is a proof-of-concept for significant changes to be made to a Linux app written in C.)

When I run the script below I see this:

connecting...                                                                                                                                                             
ex from ConnectProfile(): g-io-error-quark: GDBus.Error:org.bluez.Error.NotAvailable: Operation currently not available (36)                                              
onPropertiesChanged( org.bluez.Device1 {'Connected': True} [] )                                                                                                           
onPropertiesChanged( org.bluez.Device1 {'ServicesResolved': True} [] )                                                                                                    
onPropertiesChanged( org.bluez.Device1 {'ServicesResolved': False, 'Connected': False} [] )                                                                               

Note that the property changes happen after the call to ConnectProfile fails. I've seen suggestions that I should be opening an RFCOMM socket from inside the property-changed callback, taking advantage of the moment when the connection is open. But server-side (I'm using the excellent bluez-rfcomm-example on github) dbus/bluez takes care of creating the socket: you just get passed a file descriptor. I'm expecting ConnectProfile to work similarly, but can't find any examples.

How should I modify my new_style() function so that it gives me a working socket?

Thanks,

--Eric

#!/usr/bin/env python3

# for new_style()
from pydbus import SystemBus
from gi.repository import GLib
# for old_style()
import bluetooth

PROFILE = 'b079b640-35fe-11e5-a432-0002a5d5c51b'
ADDR = 'AA:BB:CC:DD:EE:FF'

# Works fine. But you're supposed to use dbus these days
def old_style():
    service_matches = bluetooth.find_service(uuid=PROFILE, address=ADDR)

    if len(service_matches):
        first_match = service_matches[0]
        port = first_match['port']
        host = first_match['host']

        sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
        sock.connect((host, port))

        while True:
            data = input()
            if not data:
                break
            sock.send(data)
    
        sock.close()

# Does not work. First an exception fires:
# g-io-error-quark: GDBus.Error:org.bluez.Error.NotAvailable: Operation currently not available (36)
# then onPropertiesChanged lists stuff -- after the failure, not during the connection attempt.
def new_style():
    nucky = SystemBus().get('org.bluez', '/org/bluez/hci0/dev_' + ADDR.replace(':', '_'))

    # Callback: (s, a{sv}, as)
    nucky.onPropertiesChanged = lambda p1, p2, p3: print('onPropertiesChanged(', p1, p2, p3, ')')

    def try_connect():
        print('connecting...')
        try:
            nucky.ConnectProfile(PROFILE)
        except Exception as ex:
            print('ex from ConnectProfile():', ex)
    
    GLib.timeout_add( 250, try_connect )
    GLib.MainLoop().run()

if False:
    old_style()
else:
    new_style()

(Added later)

Let me clarify my question. On a Linux box I'm running a bluez-rfcomm-example server that I modified to use a custom Service UUID. It probably creates a service record, but on the client (Android) side these three lines of Java are enough to get a connected socket to it (assuming the server has bluetooth mac AA:BB:CC:DD:EE:FF and the two are paired):

BluetoothDevice remote = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( "AA:BB:CC:DD:EE:FF" );
BluetoothSocket socket = remote.createRfcommSocketToServiceRecord( MY_SERVICE_UUID );
socket.connect();

Is there a way to do this on Linux using dbus/bluez that is remotely close to this simple? I'm assuming Device1/ConnectProfile(UUID) is what I want -- that it's the same thing as createRfcommSocketToServiceRecord() -- but that assumption might be totally wrong! Should this even be possible from Linux using blues/dbus? Or should I stick with the older methods?

Thanks, and sorry for the vague initial question.

--Eric


Solution

  • There is a good (if slightly old now) blog comparing pybluez and using Python 3 sockets: https://blog.kevindoran.co/bluetooth-programming-with-python-3/

    If you want to do it with the BlueZ D-Bus API then the key documentations is: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/profile-api.txt

    And the BlueZ example is at: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/test/test-profile

    Creating this with pydbus has some issues as documented at: https://github.com/LEW21/pydbus/issues/54