Developing Bluetooth Applications

Initialization

The Bluetooth subsystem is initialized using the bt_enable() function. The caller should ensure that function succeeds by checking the return code for errors. If a function pointer is passed to bt_enable(), the initialization happens asynchronously, and the completion is notified through the given function.

Bluetooth Application Example

A simple Bluetooth beacon application is shown below. The application initializes the Bluetooth Subsystem and enables non-connectable advertising, effectively acting as a Bluetooth Low Energy broadcaster.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

/*
 * Set Advertisement data. Based on the Eddystone specification:
 * https://github.com/google/eddystone/blob/master/protocol-specification.md
 * https://github.com/google/eddystone/tree/master/eddystone-url
 */
static const struct bt_data ad[] = {
	BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),
	BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0xaa, 0xfe),
	BT_DATA_BYTES(BT_DATA_SVC_DATA16,
		      0xaa, 0xfe, /* Eddystone UUID */
		      0x10, /* Eddystone-URL frame type */
		      0x00, /* Calibrated Tx power at 0m */
		      0x00, /* URL Scheme Prefix http://www. */
		      'z', 'e', 'p', 'h', 'y', 'r',
		      'p', 'r', 'o', 'j', 'e', 'c', 't',
		      0x08) /* .org */
};

/* Set Scan Response data */
static const struct bt_data sd[] = {
	BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
};

static void bt_ready(int err)
{
	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
		return;
	}

	printk("Bluetooth initialized\n");

	/* Start advertising */
	err = bt_le_adv_start(BT_LE_ADV_NCONN, ad, ARRAY_SIZE(ad),
			      sd, ARRAY_SIZE(sd));
	if (err) {
		printk("Advertising failed to start (err %d)\n", err);
		return;
	}

	printk("Beacon started\n");
}

void main(void)
{
	int err;

	printk("Starting Beacon Demo\n");

	/* Initialize the Bluetooth Subsystem */
	err = bt_enable(bt_ready);
	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
	}
}

The key APIs employed by the beacon sample are bt_enable() that’s used to initialize Bluetooth and then bt_le_adv_start() that’s used to start advertising a specific combination of advertising and scan response data.

Using BlueZ with Zephyr

The Linux Bluetooth Protocol Stack, BlueZ, comes with a very useful set of tools that can be used to debug and interact with Zephyr’s BLE Host and Controller. In order to benefit from these tools you will need to make sure that you are running a recent version of the Linux Kernel and BlueZ:

  • Linux Kernel 4.10+
  • BlueZ 4.45+

Additionally, some of the BlueZ tools might not be bundled by default by your Linux distribution. If you need to build BlueZ from scratch to update to a recent version or to obtain all of its tools you can follow the steps below:

git clone git://git.kernel.org/pub/scm/bluetooth/bluez.git
cd bluez
./bootstrap-configure --disable-android --disable-midi
make

You can then find btattach, btmgt and btproxy in the tools/ folder and btmon in the monitor/ folder.

You’ll need to enable BlueZ’s experimental features so you can access its most recent BLE functionality. Do this by editing the file /lib/systemd/system/bluetooth.service and making sure to include the -E option in the daemon’s execution start line:

ExecStart=/usr/libexec/bluetooth/bluetoothd -E

Finally you can reload and restart the daemon:

sudo systemctl daemon-reload
sudo systemctl restart bluetooth

Testing with QEMU

It’s possible to test Bluetooth applications using QEMU. In order to do so, a Bluetooth controller needs to be exported from the host OS (Linux) to the emulator. For this purpose you will need some tools described in the Using BlueZ with Zephyr section.

Using Host System Bluetooth Controller in QEMU

The host OS’s Bluetooth controller is connected to the second QEMU serial line using a UNIX socket. This socket gets used with the help of the QEMU option -serial unix:/tmp/bt-server-bredr. This option gets passed to QEMU through QEMU_EXTRA_FLAGS automatically whenever an application has enabled Bluetooth support.

On the host side, BlueZ allows to export its Bluetooth controller through a so-called user channel for QEMU to use:

  1. Make sure that the Bluetooth controller is down

  2. Use the btproxy tool to open the listening UNIX socket, type:

    sudo tools/btproxy -u -i 0
    Listening on /tmp/bt-server-bredr
    

    You might need to replace -i 0 with the index of the Controller you wish to proxy.

  3. Choose one of the Bluetooth sample applications located in samples/bluetooth.

  4. To run a Bluetooth application in QEMU, type:

cd $ZEPHYR_BASE/samples/bluetooth/<sample>
mkdir build && cd build
cmake -GNinja -DBOARD=qemu_x86 ..
ninja run

Running QEMU now results in a connection with the second serial line to the bt-server-bredr UNIX socket, letting the application access the Bluetooth controller.

Testing Zephyr-based Controllers with BlueZ

If you want to test a Zephyr-powered BLE Controller using BlueZ’s Bluetooth Host, you will need a few tools described in the Using BlueZ with Zephyr section. Once you have installed the tools you can then use them to interact with your Zephyr-based controller:

sudo tools/btmgmt --index 0
[hci0]# auto-power
[hci0]# find -l

You might need to replace --index 0 with the index of the Controller you wish to manage. Additional information about btmgmt can be found in its manual pages.