Learn what Felgo offers to help your business succeed. Start your free evaluation today! Felgo for Your Business

Use Bluetooth Low Energy

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.

Platform support

Currently the following platforms are supported:

  • Android
  • iOS
  • macOS
  • Linux (Using the BlueZ stack 4.x/5.x)

Bluetooth LE Protocol Introduction

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.

Services

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.

Characteristics

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.

Available QML Items

BluetoothLeCharacteristic

Bluetooth Low Energy characteristics handle data read and write

BluetoothLeDevice

Bluetooth Low Energy Device, discovered by the BluetoothLeManager component

BluetoothLeManager

Bluetooth Low Energy device discovery management

BluetoothLeService

Bluetooth Low Energy Service, groups Characteristics

Bluetooth LE Examples

Device Battery Monitor

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.

Arduino ESP32 Example

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.

Notify Example

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.

Read & Write

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
       }
     }
   }
 }

Platform Notes

  • Android
    • The ACCESS_FINE_LOCATION permission must be included in your manifest and accepted by the user.
    • The service list for devices is cached, a Bluetooth Stack reset might be required to refresh discovered device services, do so by disabling and enabling your device Bluetooth radio from the settings menu.
    • All services are presented as primary, there’s no information to identify secondary services from the OS.
  • iOS
    • Bluetooth on/off state is not provided by the OS, it’s determined from events provided by the underlying API.
    • Real Bluetooth device addresses are not accessible to protect the user privacy, device dependent UUIDs are provided instead, those UUIDs are persistent for an application on the mobile device. Keep in mind that sharing them across mobile devices does not make sense.

Further Reading

Qt_Technology_Partner_RGB_475 Qt_Service_Partner_RGB_475_padded