QtConnectivityBLE is a C++ library that collects the Linux Bluetooth work shared by my Qt applications: adapter management, device discovery, pairing, connection, and GATT service discovery. Device-specific commands and payload decoding stay in the application that understands them.
The motivation is practical. A camera controller and a heart-rate client speak different protocols, but both need to find a device, establish a connection, handle user interaction during pairing, and recover their application state when a connection ends. I wanted those common mechanics to have one implementation without turning the library into a collection of unrelated device protocols.
Three responsibilities that need separate state
A Bluetooth workflow has several milestones. Discovering a nearby device makes it a candidate for selection. Pairing establishes a bond when required. Connecting and discovering GATT services exposes the device's data and command interface. An application should not treat all of these as one “connected” flag.
The library splits the work across three components:
BluetoothAdaptertalks to BlueZ over D-Bus for adapter power, discovery, device selection, pairing, and device removal. It exposes changes in the available devices and selected device through Qt properties and signals.BluezPairingAgenthandles requests that need user input or confirmation during pairing. It translates BlueZ callbacks into prompts that an application can display in its own interface.BleTransportuses Qt Bluetooth to connect as a BLE central, discover services, and retrieve their details. It exposes ready services and connection errors to consumers.
BlueZ remains the Linux Bluetooth service underneath this design. The direct D-Bus integration gives the application control of adapter and pairing behavior, while Qt's BLE controller API provides the central-role connection and service-discovery objects.
Pairing as an asynchronous conversation
Pairing can require more than pressing a Connect button. A device may ask for a PIN, display a passkey, require numeric confirmation, or request authorization. The BlueZ Agent API delivers those requests to an application's registered agent.
I implemented the agent so the UI can respond later instead of embedding a dialog inside a D-Bus callback. A prompt includes a request ID, device, request kind, and relevant display information. The application shows the appropriate controls, then returns a confirmation or entered code using that request ID.
The agent retains the pending D-Bus request until it can finish it. It also handles rejection, cancellation, timeout, and shutdown. Input errors are reported back to the interface, and a finished signal lets it close the matching prompt. Request identity prevents a delayed button press from answering a different pairing request.
This keeps presentation choices outside the library. A desktop dialog and a touchscreen panel can use the same pairing logic while presenting different controls.
Where the generic transport stops
Once a connection is established, BleTransport asks Qt to discover the remote services. It creates service objects, discovers their details, and emits serviceReady with a QLowEnergyService when that service is ready for a consumer.
That is the handoff point to a device handler. The handler recognizes the service UUID, selects the characteristics it needs, enables notifications where appropriate, and translates byte payloads into meaningful application data. It also formats outgoing commands.
For example, a heart-rate handler understands measurement flags and BPM values. A camera handler understands camera-specific services and commands. Neither set of rules belongs in the adapter or generic transport, and neither should be scattered through QML button handlers.
The library deliberately exposes Qt's service object at this boundary. Consumers still need to understand GATT and the peripheral's protocol; the benefit is reuse of connection mechanics, not hiding the device interface behind an invented universal command API.
Lifetime and disconnects are part of the design
The transport owns its controller and discovered service objects. When a connection closes or the controller is replaced, consumers need to release their references and reset their state. Keeping a pointer to an old characteristic or service does not make it valid for the next connection.
The transport emits connectionClosed so device handlers can detach, and connectionError gives the application a message it can present or log. Before replacing an existing controller, it announces closure and disconnects the old signal connections. The next connection performs discovery again and hands fresh service objects to the handler.
The consuming application decides what to do next: retry automatically, wait for the user, clear a measurement, or show a stale-data state. Reconnection policy depends on the product, so it remains outside this small transport layer.
How I use the separation
In my heart-rate test application, the runtime connects the adapter, pairing agent, transport, and a heart-rate handler. QML receives displayable state from that composition instead of parsing Bluetooth payloads. The small client gives the shared layer a concrete consumer with a narrow protocol.
SceneSense uses the same Bluetooth foundation as part of a larger camera workflow. Its camera protocol and the transition to Wi-Fi belong above BLE transport. The library does not need to know that the application will later decode video or run an object detector.
For integration, the library provides the CMake target QtConnectivity::BLE and is linked into the application. It supplies no standalone GUI or additional daemon. The application creates the components with a deliberate lifetime and wires their signals to its controllers and device handlers.
Scope of the current implementation
The library currently targets Linux with BlueZ, C++20, and Qt 6.8 or newer. Its adapter path is fixed to hci0, so selecting among multiple adapters needs additional work. Using Qt Bluetooth for GATT does not make the entire library portable while adapter and pairing management remain tied to BlueZ.





