Thursday, December 4, 2025

0

Automate Bluetooth IoT Product Testing using Raspberry Pi and BlueZ

Introduction:

 

With the rapid spread of innovative IoT devices such as sensors, werables and wireless headphones, a lot of effort is spent on Bluetooth connectivity in the development process to ensure newly available features from the Bluetooth standard are quickly shipped to the market to create a competitive advantage. This in turns requires significant amount of verification and testing to ensure that the delivered Bluetooth solution is not buggy and does not break compatibility with existing Bluetooth devices in the field (e.g. existing Android / iOS devices).

With the introduction of Bluetooth Low Energy, LE Audio and Auracast in conjunction with existing Bluetooth Classic, the complexity and testing effort have increased. This means a firm would need to dedicate significant resources to verify their Bluetooth implementation. To reduce the cost of manual testing, a firm would likely invest in test automation to efficiently verify Bluetooth related workflows that users will perform in the field (e.g user trying to pair a smart-watch to smart-phone via Bluetooth). However, There are many challenges when it comes to Bluetooth test automation:

  • Bluetooth APIs are vendor and platform specific (e.g. If you want to test your product against Android / iOS devices, you need to maintain a lot of customization in your test framework).
  • In lack of standard high level Bluetooth APIs, you are forced in certain cases to use UI automation frameworks (e.g. Appium) to interactively automate a desired Bluetooth use case for a specific platform UI . This also adds unnecessary complexity and coupling to the testing process.
  • To have a sufficient testing coverage, you need to establish an extensive "hardware in the loop test setup" with different Bluetooth devices to test against your product. This renders a huge cost and maintenance effort for test development and execution.

Objective:

In this blog post, I share a low cost-effective solution to tackle some of the Bluetooth testing challenges listed above. The solution involves using BlueZ (Linux standard Bluetooth stack) running on Raspberry Pi (low cost micro-computer) to test Bluetooth functionality of a given product. Later I show how BlueZ D-Bus API can be utilized to communicate with BlueZ programmatically via Python and hence a Bluetooth testing application could be developed.

About BlueZ: 

BlueZ is the standard Bluetooth stack for Linux. It can handle both Classic Bluetooth (BR/EDR) and Bluetooth Low Energy (BLE), and is shipped by default with Raspbian OS (Debian-based Linux distribution) along with other Linux distributions.

The modular architecture of BlueZ provides the flexibility to control attached Bluetooth adapters regardless of the platform and runtime environment used. BlueZ communicates with a Bluetooth adapter  via the Host Controller Interface (HCI) which is a standard interface that is defined in the Bluetooth Core Specification and is the basis of all Bluetooth protocols stacks.

BlueZ community maintains a well compatible stack with support for different Bluetooth controllers from various vendors. This facilitates the process of integrating different Bluetooth chips and control them from a standard interface without having to worry about the low level implementation of different vendors.

BlueZ management dameon process "bluetoothd" runs in user space and exposes high level Bluetooth functionality via a set of interactive command line tools: (bluetoothctl, btmon, l2ping , etc..) to perform Bluetooth related workflows (e.g. scan, pair, advertise, monitor and connect to Bluetooth devices).

When it comes to Bluetooth  automation with Linux, the command line tools listed above are not much of help. After some basic search on the web, I found out that BlueZ D-Bus API seems to be the recommended way to programmatically talk to the the Bluetooth dameon. The D-Bus API utilizes Linux inter-process communication bus (D-Bus). With this approach, you can establish a standard access to control and monitor Bluetooth tasks in Linux "asynchronously" without having to worry about the low level implementation behind these tasks.

Talking to BlueZ on the D-Bus:

To interact with the BlueZ D-Bus API, various libraries and wrappers already exist. If you are using Python in your software framework (e.g. for writing automated Bluetooth tests), The following libraries are of interest and will save you time trying to look at the D-Bus message specification:

  • Bleak (asynchronous, cross-platform Python API to communicate with BLE devices) 

However, if you are still interested in the details and want to build your custom D-Bus API for Bluetooth, The following resources are beneficial to get your started:

To demonstrate the usage of the D-Bus API, I created the examples below in Python

Bluetooth Discovery via Adapter Interface:

To control the Bluetooth adapter states and scan for nearby Bluetooth devices,  we need to talk to the "org.bluez.Adapter" interface. To test the code execution, we can open a parallel terminal session with "bluetoothctl" tool running which interactively displays all messages received / emitted by "bluetoothd"

import dbus
import time

# initialize a system d-bus object
bus = dbus.SystemBus()

# based on BlueZ d-bus API get the Adapter interface method and properties
# https://github.com/bluez/bluez/blob/master/doc/org.bluez.Adapter.rst

adapter_object = bus.get_object("org.bluez", "/org/bluez/hci0") # hci0 is the default Bluetooth hardware adapter
adapter_interface = dbus.Interface(adapter_object, "org.bluez.Adapter1")
adapter_props = dbus.Interface(adapter_object, dbus.PROPERTIES_IFACE)

# read all available properties of the adapter as d-bus dictionary
print(adapter_props.GetAll('org.bluez.Adapter1'))

# read individual properties by their name:
print(adapter_props.Get("org.bluez.Adapter1", 'PowerState'))
print(adapter_props.Get("org.bluez.Adapter1", 'Address'))

# set properties of the adapter
adapter_props.Set("org.bluez.Adapter1", 'Powered', False) # -> Disable Bluetooth adapter
adapter_props.Set("org.bluez.Adapter1", 'Powered', True) # -> Enable Bluetooth adapter

# start / stop discovery of Bluetooth devices
adapter_interface.StartDiscovery() -> Bluetooth adapter Discovering state changes to yes
time.sleep(30) # scan for 30 seconds
adapter_interface.StopDiscovery() -> Bluetooth adapter Discovering state changes to no

Pairing and Connection via Device Interface:

To pair with and connect to remote Bluetooth devices, we utilize the "org.bluez.Device" interface. The code snippet below is an example of interacting with the interface:

import dbus
import time

# initialize a system d-bus object
bus = dbus.SystemBus()

# get the DBus Object Manager to access currently available BlueZ device objects
manager = dbus.Interface(bus.get_object('org.bluez', '/'), 'org.freedesktop.DBus.ObjectManager')
managed_objects = manager.GetManagedObjects()

# look for BlueZ device interface objects and print found devices info
for obj in managed_objects.values():
device = obj.get('org.bluez.Device1', None)
if device:
print(f" Name: {device['Name']} , Address: {device['Address']}, Adapter: {device['Adapter']}, IsPaired: {bool(device['Paired'])}, IsConnected: {bool(device['Connected'])}")

# pair to a remote Bluetooth device using its address and Bluetooth adapter path:
default_adapter_path = '/org/bluez/hci0'
dev_relative_path = "dev_" + device['Address'].replace(":", "_")
remote_device_path = default_adapter_path + '/' + dev_relative_path
remote_device_object = bus.get_object('org.bluez', remote_device_path)
remote_device_methods = dbus.Interface(remote_device_object, 'org.bluez.Device1')
remote_device_props = dbus.Interface(remote_device_object, dbus.PROPERTIES_IFACE)

try:
remote_device_methods.Pair()
except Exception as e:
print(f"Excpetion occured during the pairing process: {str(e)}")

# wait for host / device to complete pairing process
time.sleep(30)

try:
remote_device_methods.Connect()
except Exception as e:
print(f"Excpetion occured during the connection process: {str(e)}")

Writing a Bluetooth test case with Python:

Now we have a basic understanding of how BlueZ D-Bus API can be accessed via Python, we can start creating our Bluetooth test functions. To make things easier, we can use an existing Python library like python-bluezero with high level functions to avoid working directly with D-bus objects as shown in previous examples and it also covers wide range of use cases for Bluetooth devices without adding much of complexity. This is a personal choice of course and you might use a different library that better suits your needs or build yours from scratch based on the BlueZ D-Bus API documentation.

In general, the test architecture diagram is depicted in the figure below, where we would be able to communicate with different Bluetooth adapters attached to the Raspberry Pi simply using Python and BlueZ D-Bus interface. Since the HCI interface between BlueZ and the Bluetooth hardware is governed by the Bluetooth standard, we do not need to write platform / vendor specific test functions. This greatly simplifies the process of testing our "Bluetooth Product". Of course, BlueZ is not free of bugs and limitations, However, it is still a free, open-source and capable tool to work with the Bluetooth technology.

 

In the code snippet below I created a simple test case using python-bluezero to scan for nearby Bluetooth devices, then pair and connect to a remote Bluetooth device with a given name. After connection is successful, I perform a series of disconnection / connection requests to verify that my Bluetooth device communicates as expected with the Bluetooth adapter on the Raspberry Pi. You can extend the test case to cover more interesting scenarios (e.g. interacting with GATT servers on a BLE device, stream audio from the Pi to a Bluetooth audio device and observe the status of Bluetooth audio profiles).
 
import logging
import pytest
from bluezero import adapter, central
from time import sleep

BT_NAME = "My_Bluetooth_Device" # Declare the name of your BT device under test

# create a pytest fixture to interact with the Bluetooth adapter on the system
@pytest.fixture(scope="session")
def bt_adapter() -> adapter.Adapter:
bt_dongles = adapter.list_adapters()
logging.info(f"Available BT adapters: {[str(bt_address) for bt_address in bt_dongles]}, -> selecting first adapter")
bt_dongle = adapter.Adapter(bt_dongles[0])
if not bt_dongle.powered:
logging.info("Powering on BT adapter")
bt_dongle.powered = True
return bt_dongle

def test_bt_pairing_and_connection(bt_adapter: adapter.Adapter) -> None:

logging.info(f"Discovering nearby BT devices ...")
target_bt_dev = None
bt_adapter.nearby_discovery(timeout=15) # scan for 15 seconds

# check target device is found
for bt_dev in central.Central.available(bt_adapter.address):
if bt_dev.name is not None and BT_NAME == bt_dev.name:
target_bt_dev = bt_dev

assert target_bt_dev, f"'{BT_NAME}' was not found"

logging.info(f"Pairing with: '{bt_dev.name} : {bt_dev.address}'...")
target_bt_dev.trusted = True
target_bt_dev.pair()
sleep(10) # allow sometime to resolve device info / services

# attempt connection after pairing / if device did not automatically connect
if target_bt_dev.connected == False:
target_bt_dev.connect()
sleep(10) # allow sometime for device to sync info

# check device is now connected
assert target_bt_dev.connected == True

# perfom disconnection / reconnection iterations
for _ in range(3):
target_bt_dev.disconnect()
sleep(10)
assert target_bt_dev.connected == False
target_bt_dev.connect()
sleep(10)
assert target_bt_dev.connected == True

0 comments:

Post a Comment