How do I run a script when a Bluetooth device connects?

I didn't like the polling approach, so I did some digging on bluez and DBus. I ended up writing the following script:

#!/usr/bin/python

import dbus
from dbus.mainloop.glib import DBusGMainLoop
import gobject

import subprocess

# ID of the device we care about
DEV_ID = '00_1D_54_AB_DC_72'

dbus_loop = DBusGMainLoop()
bus = dbus.SystemBus(mainloop=dbus_loop)

# Figure out the path to the headset
man = bus.get_object('org.bluez', '/')
iface = dbus.Interface(man, 'org.bluez.Manager')
adapterPath = iface.DefaultAdapter()

headset = bus.get_object('org.bluez', adapterPath + '/dev_' + DEV_ID)
    # ^^^ I'm not sure if that's kosher. But it works.

def cb(iface=None, mbr=None, path=None):

    if ("org.bluez.Headset" == iface and path.find(DEV_ID) > -1):
        print 'iface: %s' % iface
        print 'mbr: %s' % mbr
        print 'path: %s' % path
        print "\n"
        print "matched"

        if mbr == "Connected":
            subprocess.call(["clementine", "--play"])
            print 'conn'

        elif mbr == "Disconnected":
            subprocess.call(["clementine", "--stop"])
            print 'dconn'

headset.connect_to_signal("Connected", cb, interface_keyword='iface', member_keyword='mbr', path_keyword='path')
headset.connect_to_signal("Disconnected", cb, interface_keyword='iface', member_keyword='mbr', path_keyword='path')

loop = gobject.MainLoop()
loop.run()

To discover a successfully established Bluetooth connection we can run

sdptool browse xx:xx:xx:xx:xx:xx

By this the SDB connection will be tested for a connection to the given MAC address. It may take considerable time until browsing times out with an error like

Failed to connect to SDP server on 00:0C:78:4F:B6:B5: Host is down

We don't know the exact purpose of your script, but most likely you wish to play audio via Clementine when a headset was connected.

Then we could just see whether there is a Bluetooth audio sink with

pacmd list-sinks | grep xx_xx_xx_xx_xx_xx

Where xx_xx_xx_xx_xx_xx is the MAC address (: needs to be replaced with _). The output will then tell you whether there is a Bluetooth audio sink available or nothing if not.

See this answer on how to switch audio to this sink.


Stream2ip

With stream2ip we can define a shell command or a script to run after a connection was established. There also is an option to automatically start a supported media player after a connection was established:

enter image description here

Stream2ip will also try to reconnect the currently running playback stream to the Bluetooth audio device in case the connection was interrupted.


@Erigami Your answer helped a lot but to make it work I'd to do some changes. I'm using ubuntu 14.04.

#!/usr/bin/python

import dbus
from dbus.mainloop.glib import DBusGMainLoop
import gobject

import subprocess

# ID of the device we care about
DEV_ID = 'CC:C3:EA:A5:16:90'.replace(":", "_")

dbus_loop = DBusGMainLoop()
bus = dbus.SystemBus(mainloop=dbus_loop)

# Figure out the path to the headset
man = bus.get_object('org.bluez', '/')
iface = dbus.Interface(man, 'org.bluez.Manager')
adapterPath = iface.DefaultAdapter()

print(adapterPath + '/dev_' + DEV_ID)
headset = bus.get_object('org.bluez', adapterPath + '/dev_' + DEV_ID)
# ^^^ I'm not sure if that's kosher. But it works.

def cb(*args, **kwargs):
    is_connected = args[-1]
    if isinstance(is_connected, dbus.Boolean) and is_connected:
        print("Connected")
    elif isinstance(is_connected, dbus.Boolean) and not is_connected:
        print("Disconnected")

headset.connect_to_signal("PropertyChanged", cb, interface_keyword='iface', member_keyword='mbr', path_keyword='path')

loop = gobject.MainLoop()
loop.run()

Still if this does not work then use and monitor system dbus.

dbus-monitor --system

d-feet can be used further. It is GUI tool to watch dbus objects.