Embedded Linux Bluetooth Development with BlueZ and BLE
Bluetooth remains one of the most practical short-range wireless technologies for embedded systems. It provides a reasonable balance between power consumption, implementation complexity, cost, and ecosystem maturity, making it ideal for industrial devices, IoT gateways, handheld equipment, and wireless peripherals.
On Linux systems, Bluetooth development almost always revolves around BlueZ, the official Linux Bluetooth protocol stack maintained by the Linux community. Beyond being a protocol stack, BlueZ also provides a large set of user-space tools, daemons, APIs, and debugging utilities.
This article walks through a complete Bluetooth communication workflow in Embedded Linux:
- Hardware initialization
- Device pairing and connection
- Classic Bluetooth RFCOMM serial communication
- BLE GATT interaction
- Programmatic control through C code
The examples focus on practical command-line workflows and low-level Linux integration suitable for embedded developers.
🧰 Experimental Environment and Setup #
The test platform used in this article consists of:
- A custom Yocto-based Linux system
- Linux kernel 5.15
- USB Bluetooth adapter using the CSR8510 chipset
- BlueZ user-space stack
Most generic USB Bluetooth adapters rely on the btusb kernel driver, which modern kernels usually load automatically.
After inserting the adapter, verify that the hardware is recognized correctly:
dmesg | grep Bluetooth
hciconfig -a
The first command checks kernel logs for Bluetooth initialization messages.
The second command displays detailed information about available HCI devices. The hciconfig utility behaves similarly to ifconfig, but specifically for Bluetooth Host Controller Interface (HCI) devices.
A correctly initialized adapter typically appears as:
hci0: Type: Primary Bus: USB
BD Address: XX:XX:XX:XX:XX:XX
UP RUNNING
If the system reports that hciconfig is missing, install the BlueZ packages.
On Ubuntu:
sudo apt install bluez bluez-tools
On embedded systems, BlueZ usually needs to be included during Yocto image generation or cross-compiled manually.
Next, start the Bluetooth daemon:
systemctl start bluetooth
systemctl status bluetooth
At this point, the low-level Bluetooth stack should be operational.
🧠 Understanding the BlueZ Architecture #
BlueZ is built around the bluetoothd daemon running in user space.
Internally:
bluetoothdmanages devices and protocols- D-Bus acts as the communication layer
- Client tools interact with BlueZ through D-Bus APIs
The commonly used bluetoothctl utility is simply an interactive frontend that translates user commands into D-Bus operations.
The relationship can be simplified as:
User Command → bluetoothctl → D-Bus → bluetoothd → Bluetooth Hardware
Understanding this architecture becomes important later when integrating Bluetooth into embedded applications programmatically.
🔗 Pairing and Connecting with bluetoothctl #
Launch the interactive management shell:
sudo bluetoothctl
The shell prompt changes to:
[bluetooth]#
A typical Bluetooth initialization sequence looks like this:
power on
agent on
default-agent
scan on
What Each Command Does #
power on
#
Enables the Bluetooth adapter.
The adapter state changes from:
Powered: no
to:
Powered: yes
agent on
#
Starts a Bluetooth agent responsible for handling authentication and pairing requests.
Without an active agent, pairing operations may fail because no process is available to handle PIN confirmation or authorization requests.
default-agent
#
Registers the active agent as the system-wide default.
This allows automatic handling of pairing dialogs and confirmation prompts.
scan on
#
Starts device discovery.
Nearby Bluetooth devices begin appearing in the terminal output along with:
- MAC address
- Device name
- Device type
Example:
[NEW] Device 11:22:33:44:55:66 HC-05
Once the target device appears, stop scanning:
scan off
Then initiate pairing:
pair 11:22:33:44:55:66
Some classic Bluetooth modules such as the HC-05 require a PIN code, commonly:
1234
After pairing succeeds, mark the device as trusted and connect:
trust 11:22:33:44:55:66
connect 11:22:33:44:55:66
Successful connection output looks similar to:
Connection successful
You can inspect device capabilities using:
info 11:22:33:44:55:66
Trusted devices automatically reconnect later without repeating the pairing procedure.
📡 Using Bluetooth as a Wireless Serial Port #
Classic Bluetooth supports the Serial Port Profile (SPP), which emulates serial communication over RFCOMM.
This is extremely useful for:
- MCU debugging
- Transparent UART replacement
- Wireless telemetry
- Industrial serial bridging
After connecting to an SPP-capable device, determine the RFCOMM channel number.
Most HC-05 style modules use:
RFCOMM Channel 1
You can verify this using:
sdptool browse
Bind the remote serial service to a local device node:
sudo rfcomm bind 0 11:22:33:44:55:66 1
This creates:
/dev/rfcomm0
The Bluetooth link now behaves like a standard Linux serial device.
Receiving Data #
Open a terminal and monitor incoming traffic:
sudo cat /dev/rfcomm0
Sending Data #
In another terminal:
echo "hello from i.MX6" | sudo tee /dev/rfcomm0
The remote side immediately receives the message.
This effectively creates a wireless UART bridge.
Releasing the Binding #
After use, release the RFCOMM binding:
sudo rfcomm release 0
Unlike physical UARTs, RFCOMM devices do not require baud-rate configuration because Bluetooth internally handles timing and transport.
For many embedded debugging tasks, lightweight tools such as cat and echo are often more reliable than full terminal emulators.
📶 Working with BLE Devices Through GATT #
Bluetooth Low Energy (BLE) no longer uses serial-style communication.
Instead, BLE relies on:
- ATT (Attribute Protocol)
- GATT (Generic Attribute Profile)
Data is organized as:
- Services
- Characteristics
- Descriptors
BlueZ provides a built-in GATT interface directly inside bluetoothctl.
Scanning for BLE Devices #
Start BLE scanning explicitly:
scan le
Once the device is found:
trust 11:22:33:44:55:66
connect 11:22:33:44:55:66
Entering the GATT Menu #
Switch into GATT mode:
menu gatt
List all available attributes:
list-attributes
Example output:
/org/bluez/hci0/dev_11_22_33_44_55_66/service000a
/org/bluez/hci0/dev_11_22_33_44_55_66/service000a/char000b
Each characteristic includes:
- UUID
- Access permissions
- Notification support
- Read/write capability
Reading Characteristic Values #
Select an attribute:
select-attribute /org/bluez/hci0/dev_11_22_33_44_55_66/service000a/char000b
Read the value:
read
Output appears in hexadecimal form.
Example:
54 65 6d 70 20 31
Decoded ASCII:
Temp 1
Subscribing to Notifications #
Enable notifications:
notify on
Incoming updates appear automatically:
[CHG] Attribute ... Value: ...
Writing Characteristic Values #
Example:
write 0x01
The meaning depends entirely on the target device protocol.
Common use cases include:
- Enabling notifications
- Sending control commands
- Triggering measurements
- Configuring sensors
Once finished:
back
returns to the main menu.
For rapid BLE debugging and reverse engineering, bluetoothctl is surprisingly powerful and often eliminates the need for external GUI tools.
💻 Programmatic Bluetooth Control #
Interactive debugging is useful during development, but production systems require application-level integration.
Several implementation approaches exist.
Shell Script Automation #
Simple workflows can use:
bluetoothctl --- Bash scripts
expect
However, these approaches become difficult to maintain for larger projects.
D-Bus API Integration #
The preferred approach is communicating directly with BlueZ through D-Bus APIs.
Common choices include:
| Language | Common Library |
|---|---|
| Python | pydbus, bleak |
| C | libdbus, GDBus |
| C++ | sdbus-c++ |
D-Bus provides full control over:
- Device scanning
- Pairing
- Connections
- GATT discovery
- Characteristic read/write
- Notification handling
For production-grade embedded applications, D-Bus integration is usually the cleanest architecture.
⚙️ BLE Scanning Through HCI Sockets in C #
BlueZ also exposes low-level HCI interfaces for direct controller communication.
The following example demonstrates a simple BLE scanner implemented in C using:
- HCI sockets
libbluetooth- Passive scanning mode
The program:
- Opens
hci0 - Configures BLE scan parameters
- Receives advertising packets
- Displays:
- Device address
- RSSI
- Raw advertising data
Compile the program using:
gcc ble_scan.c -o ble_scan -lbluetooth
Example execution:
./ble_scan 5
The scan duration defaults to 5 seconds.
Key Concepts Demonstrated #
The example illustrates several important low-level Bluetooth concepts:
HCI Device Access #
sock = hci_open_dev(dev_id);
This opens direct communication with the Bluetooth controller.
Event Filtering #
hci_filter_set_event(EVT_LE_META_EVENT, &flt);
Only BLE-related events are received.
Scan Configuration #
scan_params_cp.type = 0x01;
This enables passive scanning mode.
Advertising Packet Parsing #
evt_le_meta_event *meta
Advertising packets are decoded directly from HCI events.
Why HCI Sockets Matter #
Direct HCI access is useful for:
- Protocol analyzers
- BLE sniffers
- Manufacturing tools
- Low-level debugging
- Custom Bluetooth stacks
However, implementing complete GATT functionality directly on raw HCI sockets becomes extremely complex.
For higher-level BLE communication, D-Bus remains the preferred engineering approach.
🚀 Final Thoughts #
BlueZ provides an extremely capable Bluetooth ecosystem for Embedded Linux systems, ranging from simple command-line pairing workflows to full low-level HCI access.
For embedded developers, the practical workflow usually evolves through several stages:
- Validate hardware with
hciconfig - Debug connections using
bluetoothctl - Prototype RFCOMM or GATT communication
- Integrate D-Bus APIs into production applications
The tooling may initially appear fragmented, but once the BlueZ architecture becomes familiar, Linux Bluetooth development becomes remarkably flexible and powerful.
Whether building:
- Industrial gateways
- Wireless sensor systems
- BLE peripherals
- Embedded AI edge devices
- Serial replacement links
BlueZ remains one of the most mature and production-proven Bluetooth stacks available in the embedded Linux ecosystem.