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 -aThe 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 RUNNINGIf the system reports that hciconfig is missing, install the BlueZ packages.
On Ubuntu:
sudo apt install bluez bluez-toolsOn 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 bluetoothAt 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 HardwareUnderstanding this architecture becomes important later when integrating Bluetooth into embedded applications programmatically.
🔗 Pairing and Connecting with bluetoothctl #
Launch the interactive management shell:
sudo bluetoothctlThe shell prompt changes to:
[bluetooth]#A typical Bluetooth initialization sequence looks like this:
power on
agent on
default-agent
scan onWhat Each Command Does #
power on
#
Enables the Bluetooth adapter.
The adapter state changes from:
Powered: noto:
Powered: yesagent 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-05Once the target device appears, stop scanning:
scan offThen initiate pairing:
pair 11:22:33:44:55:66Some classic Bluetooth modules such as the HC-05 require a PIN code, commonly:
1234After pairing succeeds, mark the device as trusted and connect:
trust 11:22:33:44:55:66
connect 11:22:33:44:55:66Successful connection output looks similar to:
Connection successfulYou can inspect device capabilities using:
info 11:22:33:44:55:66Trusted 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 1You can verify this using:
sdptool browseBind the remote serial service to a local device node:
sudo rfcomm bind 0 11:22:33:44:55:66 1This creates:
/dev/rfcomm0The Bluetooth link now behaves like a standard Linux serial device.
Receiving Data #
Open a terminal and monitor incoming traffic:
sudo cat /dev/rfcomm0Sending Data #
In another terminal:
echo "hello from i.MX6" | sudo tee /dev/rfcomm0The 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 0Unlike 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 leOnce the device is found:
trust 11:22:33:44:55:66
connect 11:22:33:44:55:66Entering the GATT Menu #
Switch into GATT mode:
menu gattList all available attributes:
list-attributesExample output:
/org/bluez/hci0/dev_11_22_33_44_55_66/service000a
/org/bluez/hci0/dev_11_22_33_44_55_66/service000a/char000bEach 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/char000bRead the value:
readOutput appears in hexadecimal form.
Example:
54 65 6d 70 20 31Decoded ASCII:
Temp 1Subscribing to Notifications #
Enable notifications:
notify onIncoming updates appear automatically:
[CHG] Attribute ... Value: ...Writing Characteristic Values #
Example:
write 0x01The meaning depends entirely on the target device protocol.
Common use cases include:
- Enabling notifications
- Sending control commands
- Triggering measurements
- Configuring sensors
Once finished:
backreturns 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 -lbluetoothExample execution:
./ble_scan 5The 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 *metaAdvertising 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.