HOWTO/DiscoveringDevices

Description

The Bluetooth inquiry procedure has a high cost. The standard inquiry procedure takes 10.24 seconds and it is blocking. The standard inquiry result event doesn't contain the remote name. The extended inquiry response feature that provides extra information(name of the device, list of services and other information) was added in the Bluetooth Core Specification Version 2.1. Due these problems and aiming the API backward compatibility the following constraints/considerations were adopted:

  1. Just one request(per time) can be accept
  2. Use cached(stored) names if possible
  3. external request(hcitool or other apps) can't screw up the discovery procedure
  4. Name resolving(if applied) must be execute after the inquiry completed event
  5. More than one inquiry result event can be received from the same remote device
  6. RemoteDeviceFound and RemoteDeviceName (keep compatibility)

Currently three types are supported:

  1. Discovery with name resolving: Devices found during the inquiry are stored and their names are requested after the Inquiry Complete event.
  2. Discovery without name resolving: Standard inquiry procedure
  3. Periodic discovery: used to configure the Bluetooth device to enter the Periodic Inquiry Mode that performs an automatic Inquiry. Periodic discovery does not block while in use.

The discovery methods description and some detailed message sequence chart can be found at Adapter interface document.

Development warnings

  1. RemoteNameUpdated is sent once in the discovery procedure. When the name is cached it is sent after the first RemoteDeviceFound signal (applied for DiscoverDevices and DiscoverDevicesWithoutNameResolving) or it is sent in the 'name resolution' phase, after the inquiry complete event(applied for DiscoverDevices only).
  2. If the discovery is running the bonding procedure will not be allowed
  3. If there is pending bonding the DiscoverDevices will not be allowed
  4. Discovery is automatically canceled when the requestor exits.
  5. Signals DiscoveryStarted and DiscoveryCompleted can be used to find out when devices are blocked.
  6. While in periodic discovery there are no emission of DiscoveryStarted and DiscoveryCompleted.

Python examples

The following example request the standard discovery and listen for RemoteDeviceFound and RemoteNameUpdated signals:

import dbus
import dbus.glib
import gobject

def disc_started_signal():
        print 'Signal: DiscoveryStarted()'

def rem_dev_found_signal(address, cls, rssi):
        print 'Signal: RemoteDeviceFound(%s, %s, %s)' % (address, cls, rssi)

def rem_dev_name_signal(address, name):
        print 'Signal: RemoteNameUpdated(%s, %s)' % (address, name)

def disc_completed_signal():
        print 'Signal: DiscoveryCompleted()'
        main_loop.quit()

bus = dbus.SystemBus();

bus.add_signal_receiver(disc_started_signal, 'DiscoveryStarted', 'org.bluez.Adapter', 'org.bluez', '/org/bluez/hci0')
bus.add_signal_receiver(rem_dev_found_signal, 'RemoteDeviceFound', 'org.bluez.Adapter', 'org.bluez', '/org/bluez/hci0')
bus.add_signal_receiver(rem_dev_name_signal, 'RemoteNameUpdated', 'org.bluez.Adapter', 'org.bluez', '/org/bluez/hci0')
bus.add_signal_receiver(disc_completed_signal, 'DiscoveryCompleted', 'org.bluez.Adapter', 'org.bluez', '/org/bluez/hci0')

obj = bus.get_object('org.bluez', '/org/bluez/hci0')
adapter = dbus.Interface(obj, 'org.bluez.Adapter')

adapter.DiscoverDevices()

gobject.threads_init()
dbus.glib.init_threads()
main_loop = gobject.MainLoop()
main_loop.run()

GLIB with C examples

This example is the same as the one above but ported to C using GLIB for the DBUS-signals.

marshal.list

VOID:STRING,UINT,INT
VOID:STRING,STRING

bluetooth.c

/* Build with: */
/* glib-genmarshal --prefix=marshal marshal.list --header > marshal.h */
/* glib-genmarshal --prefix=marshal marshal.list --body > marshal.c */
/* gcc `pkg-config --libs --cflags glib-2.0 dbus-1 dbus-glib-1` bluetooth.c marshal.c -o bluetooth */

#include <stdlib.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>
#include <glib-object.h>

#include "marshal.h"

static GMainLoop *loop = NULL;

static void remote_device_found(DBusGProxy *object, const char *address, const unsigned int class, const int rssi, gpointer user_data)
{
        g_print("Signal: RemoteDeviceFound(%s, %d, %d)\n", address, class, rssi);
}

static void discovery_started(DBusGProxy *object, gpointer user_data)
{
        g_print("Signal: DiscoveryStarted()\n");
}

static void remote_name_updated(DBusGProxy *object, const char *address, const char *name, gpointer user_data)
{
        g_print("Signal: RemoteNameUpdated(%s, %s)\n", address, name);
}

static void discovery_completed(DBusGProxy *object, gpointer user_data)
{
        g_print("Signal: DiscoveryCompleted()\n");
        g_main_loop_quit (loop);
}

static void run_mainloop (void)
{
        GMainContext *ctx;

        ctx = g_main_loop_get_context (loop);

        while (g_main_context_pending (ctx))
                g_main_context_iteration (ctx, FALSE);
}

int main(int argc, char* argv[])
{
        GError *error = NULL;
        DBusGConnection * bus = NULL;
        DBusGProxy * obj = NULL;

        g_type_init();
        g_log_set_always_fatal (G_LOG_LEVEL_WARNING);
        loop = g_main_loop_new (NULL, FALSE);

        bus = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
        if (error != NULL)
        {
                g_printerr("Connecting to system bus failed: %s\n", error->message);
                g_error_free(error);
                exit(EXIT_FAILURE);
        }

        obj = dbus_g_proxy_new_for_name(bus, "org.bluez", "/org/bluez/hci0", "org.bluez.Adapter");

        dbus_g_object_register_marshaller(marshal_VOID__STRING_UINT_INT, G_TYPE_NONE, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INT, G_TYPE_INVALID);
        dbus_g_proxy_add_signal(obj, "RemoteDeviceFound", G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INT, G_TYPE_INVALID);
        dbus_g_proxy_connect_signal(obj, "RemoteDeviceFound", G_CALLBACK(remote_device_found), bus, NULL);

        dbus_g_proxy_add_signal(obj, "DiscoveryStarted", G_TYPE_INVALID);
        dbus_g_proxy_connect_signal(obj, "DiscoveryStarted", G_CALLBACK(discovery_started), bus, NULL);

        dbus_g_proxy_add_signal(obj, "DiscoveryCompleted", G_TYPE_INVALID);
        dbus_g_proxy_connect_signal(obj, "DiscoveryCompleted", G_CALLBACK(discovery_completed), bus, NULL);

        dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING, G_TYPE_NONE, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
        dbus_g_proxy_add_signal(obj, "RemoteNameUpdated", G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
        dbus_g_proxy_connect_signal(obj, "RemoteNameUpdated", G_CALLBACK(remote_name_updated), NULL, NULL);
        
        dbus_g_proxy_call(obj, "DiscoverDevices", &error, G_TYPE_INVALID, G_TYPE_INVALID);
        if (error != NULL)
        {
                g_printerr("Failed to discover devices: %s\n", error->message);
                g_error_free(error);
                exit(EXIT_FAILURE);
        }

        run_mainloop ();
        dbus_g_connection_flush (bus);
        g_main_loop_run (loop);
        dbus_g_connection_unref(bus);

        return 0;
}

Vala

It's also possible to use Vala to generate the necessary GLIB/C code for you. This example is the same as above written in Vala.

bluetooth.vala

/* Build with: */
/* valac --pkg dbus-glib-1 -o bluetooth bluetooth.vala */

using GLib;
using BlueZ;

[DBusInterface (name = "org.bluez.Adapter")]
interface BlueZ.BlueZInterface;


public class BlueZDiscovery : Object {

        private DBus.Connection conn;
        private BlueZInterface bluez;
        public MainLoop loop { set; get; }

        public void run () {
                conn = DBus.Bus.get (DBus.BusType.SYSTEM);

                bluez = conn.get_object<BlueZInterface> ("org.bluez", "/org/bluez/hci0");

                // dbus signals
                bluez.RemoteDeviceFound += remote_device_found;
                bluez.DiscoveryStarted += discovery_started;
                bluez.DiscoveryCompleted += discovery_completed;
                bluez.RemoteNameUpdated += remote_name_updated;

                // async dbus call
                bluez.DiscoverDevices ();

        }

        [InstanceLast]
        private void remote_device_found (BlueZInterface bluez, string address_, uint class_, int rssi_) {
                message ("Signal: RemoteDeviceFound(%s, %d, %d)", address_, class_, rssi_);
        }

        private void discovery_started(BlueZInterface bluez) {
                message ("Signal: DiscoveryStarted()");
        }

        private void remote_name_updated(BlueZInterface bluez, string address_, string name_) {
                message ("Signal: RemoteNameUpdated(%s, %s)", address_, name_);
        }

        private void discovery_completed(BlueZInterface bluez) {
                message ("Signal: DiscoveryCompleted()");
                loop.quit();
        }

        static int main (string[] args) {
                MainLoop loop = new MainLoop (null, false);

                BlueZDiscovery test = new BlueZDiscovery ();
                test.loop = loop;
                test.run ();

                loop.run ();

                return 0;
        }
}