简体   繁体   中英

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

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

The code below works to get and use a rfcomm socket for a connection to a remote service specified by a UUID. The answer I accepted, by ukBaz, included all I needed, but I didn't understand enough background to make sense of it immediately. I was right that calling ConnectProfile() was the way to start, but missed two things:

  • Providing a Profile on the calling side was necessary for two reasons. First, it provides a callback by which you get hold of the socket. But without it -- without the NewConnection method specifically -- the connection fails (ConnectProfile() returns an error.)
  • I needed to make the ConnectProfile() call on a background thread. The callback will come in on the glib loop's main thread, so ConnectProfile(), which doesn't return until the connection succeeds or fails, mustn't block that thread!

It's possible that different Bluetooth connection types require subtly different machinations, but for RFCOMM socket connections anyway this does the trick.

#!/usr/bin/env python3

import socket, threading
from pydbus import SystemBus
from gi.repository import GLib
import dbus, dbus.service, dbus.mainloop.glib

CUSTOM_UUID = 'b079b640-35fe-11e5-a432-0002a5d5c51b'
ADDR = 'AA:BB:CC:DD:EE:FF'
PATH = '/org/neednt/match/remote'

class Profile(dbus.service.Object):
    @dbus.service.method("org.bluez.Profile1",
                     in_signature="oha{sv}", out_signature="")
    def NewConnection(self, path, fd, properties):
        None
        print('NewConnection: fd:', fd);

        try:
            self.socket = socket.socket(fileno=fd.take())
            print('got socket:', self.socket)
            self.socket.send(b"You there?")
        except Exception as ex:
            print('ex:', ex)

def connect_thread_main():
    print('connect_thread_main()...')
    SystemBus().get('org.bluez', '/org/bluez/hci0/dev_' + ADDR.replace(':', '_')) \
               .ConnectProfile(CUSTOM_UUID)

dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)

bus = dbus.SystemBus()
Profile(bus, PATH)               # added by side-effect apparently

dbus.Interface(bus.get_object("org.bluez","/org/bluez"),
               "org.bluez.ProfileManager1") \
    .RegisterProfile(PATH, CUSTOM_UUID, {})

threading.Thread(target=connect_thread_main).start()
GLib.MainLoop().run()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM