Connect to Bluetooth Low Energy (Bluetooth LE or BLE) devices from QML.
The BLE protocol provides a simple and interoperable way of describing devices and services. The QML API wraps the Qt Bluetooth Low Energy implementation. This provides you with a fast and simple way to integrate IoT, wearable and custom devices to your apps and games.
Currently the following platforms are supported:
The Bluetooth LE is a client-server protocol. Peripherals behave as servers, providing services such as Heart Rate monitors. Servers advertise themselves and control connection using the Bluetooth LE GAP protocol. Clients consume the services and are also known as central devices. Some devices can advertise but will not accept connections. One such example are beacons, providing unidirectional data broadcast.
We will refer to the Generic Attribute Profile (GATT) provided on top of the Attribute Protocol (ATT) as “Bluetooth LE”. Even though there are multiple protocols and profiles defined in the Bluetooth 4.0+ specifications, the former is the most commonly used for wearables and custom devices.
Bluetooth LE services group one or more input and output logical nodes, called Characteristics. Services are identified by a single 128bit unique identifier UUID. Short 16 bit identifier versions are used for standard services. Custom services must use full size 128 bit identifiers. Devices usually also provide a name string for the service.
Are the actual logical input and output nodes. Like services a 128 bits unique identifier is used, and 16 bit short versions are provided for official definitions. Data can be read, write or both as well as notified for changes from the server. Characteristics enclose descriptors that contain metadata about the characteristic and it’s value as well as properties that carry information about read and write permissions, enable notifications, among other usages.
Binary data is used as a characteristic value. It is up to the peripheral or central to make sense of the data transferred, and must know the format, size and order beforehand. The characteristic presentation format descriptor might be provided by the server to help the central parse and format the binary data.
Predefined services are provided by the Bluetooth SIG as XML representations that depict standard use cases with the corresponding configurations for services, characteristics and descriptors.
Bluetooth Low Energy characteristics handle data read and write |
|
Bluetooth Low Energy Device, discovered by the BluetoothLeManager component |
|
Bluetooth Low Energy device discovery management |
|
Bluetooth Low Energy Service, groups Characteristics |
This example shows you how to read the battery of a Bluetooth Low Energy device that is battery operated and provides the Battery Service. To emulate such device you and quickly test a Bluetooth connection you can use two mobile devices, at least one of them an Android device. Install the BLE Peripheral Simulator app on an android device and run the Battery Service emulation.
import Felgo import QtQuick App { NavigationStack { AppPage { title: qsTr("BLE Battery") Rectangle { anchors.centerIn: parent width: parent.width * 0.6 height: parent.height * 0.8 radius: dp(30) border.color: 'black' border.width: dp(5) Rectangle { anchors.bottom: parent.bottom width: parent.width height: parent.height * batteryCharacteristic.value / 100 color: batteryCharacteristic.value > 80 ? 'green' : (batteryCharacteristic.value > 30 ? 'orange' : 'red') radius: parent.radius } } AppText { anchors.centerIn: parent text: batteryCharacteristic.value + '%' fontSize: dp(15) } } } BluetoothLeManager { discoveryRunning: true BluetoothLeDevice{ id: myBleDevice BluetoothLeService { uuid: 0x180F // Battery Service BluetoothLeCharacteristic { id: batteryCharacteristic uuid: 0x2A19 // Battery Characteristic dataFormat: 4 // 0x04 for uint8 onValueChanged: { // Value updates console.log('Battery Level Changed', value) } onValidChanged: { // Read initial value once characteristic is valid if (valid) { read() } } } } onConnectedChanged: { // Reconnect logic if (!connected) { console.log('Trying to reconnect') connect() } } } onDeviceDiscovered: device => { // Match device with service UUID if (device.services.indexOf('{0000180f-0000-1000-8000-00805f9b34fb}') > -1) { myBleDevice.setDevice(device, true) discoveryRunning = false } } } }
The device is matched by the filter on the onDeviceDiscovered callback, by using the string representation of the 128bit UUID of the battery service, once your Felgo application discovers the device a connection request is sent, service discovery requires an active connection, after you set the device in the connect method a service discovery starts. An automatic write to the Notify property is sent so updates are sent from the server device.
You can slide the battery control and press the notify button, immediately the BluetoothLeCharacteristic value is updated in your QML code.
The ESP32 is a powerful Microcontroller Unit that incorporates WiFI and Bluetooth LE radio and stack. You can use the ESP32 Arduino libraries to test Characteristic read, notification and write with your Felgo apps.
Run the following example in your Felgo QML application. Once connected, a value notification is sent every 3 ms which shows the capabilities of the protocol.
import Felgo import QtQuick App { NavigationStack { AppPage { title: qsTr("ESP32 Notify") AppText { anchors.centerIn: parent text: notifyCharacteristic.value fontSize: dp(15) } } } BluetoothLeManager { discoveryRunning: true BluetoothLeDevice{ id: myBleDevice BluetoothLeService { uuid: '{4fafc201-1fb5-459e-8fcc-c5c9c331914b}' // Custom notify Service BluetoothLeCharacteristic { id: notifyCharacteristic uuid: '{beb5483e-36e1-4688-b7f5-ea07361b26a8}' // Value dataFormat: 8 // For uint32 } } onConnectedChanged: { // Reconnect logic if (!connected) { console.log('Trying to reconnect') connect() } } } onDeviceDiscovered: device => { // To keep it simple, set your device reported name here if (device.name == 'ESP32') { myBleDevice.setDevice(device, true) discoveryRunning = false } } } }
Use the BLE_notify example for the ESP32, to do so open the example from the Arduino file menu, compile and upload your code to the device, you can monitor the serial output of your device in order to check the connection status of the mobile device running your Felgo Application.
This example reads a string from the ESP32 device and writes a string to the same characteristic. A formatting function shows how to parse a string to a Javascript ArrayBuffer. Writing sends raw binary data to the BLE device. After writing a read is triggered to update the value, since notifications are not supported in the custom device. Inspecting the ESP32 serial output confirms the write is successful.
import Felgo import QtQuick App { NavigationStack { AppPage { title: qsTr("ESP32 Read & Write") Rectangle { width: dp(20) height: width radius: width/2 anchors.margins: dp(8) anchors.top: parent.top anchors.right: parent.right color: myBleDevice.connected ? 'green' : 'red' } Column { anchors.centerIn: parent spacing: dp(15) AppText { anchors.horizontalCenter: parent.horizontalCenter text: stringCharacteristic.value fontSize: dp(15) } AppTextField { anchors.horizontalCenter: parent.horizontalCenter id: textField text: 'Hello from Felgo!' width: dp(200) } AppButton { anchors.horizontalCenter: parent.horizontalCenter text: 'Write' enabled: stringCharacteristic.valid && myBleDevice.connected onClicked: { textField.focus = false stringCharacteristic.formatWrite(textField.text) stringCharacteristic.read() } } } } } BluetoothLeManager { discoveryRunning: true BluetoothLeDevice { id: myBleDevice BluetoothLeService { uuid: '{4fafc201-1fb5-459e-8fcc-c5c9c331914b}' // Custom read/write Service BluetoothLeCharacteristic { id: stringCharacteristic uuid: '{beb5483e-36e1-4688-b7f5-ea07361b26a8}' // Value dataFormat: 0x19 //for uint32 function formatWrite(text) { let data = new Uint8Array(text.length) for (var i = 0; i < text.length; i++) { data[i] = text.charCodeAt(i) } write(data.buffer) } onValidChanged: { // Read initial value once characteristic is valid if (valid) { read() } } } } onConnectedChanged: { // Reconnect logic if (!connected) { console.log('Trying to reconnect') connect() } } } onDeviceDiscovered: device => { // Match device with service UUID if (device.services.indexOf('{4fafc201-1fb5-459e-8fcc-c5c9c331914b}') > -1) { myBleDevice.setDevice(device, true) discoveryRunning = false } } } }