Qt-based BB10 API Examples Documentation

Contents

Bluetooth GATT Example

Files:

Description

The Bluetooth GATT example demonstrates how to use the Bluetooth functionality as provided by the BB10 platform to retrieve information from Bluetooth devices that offer the low-energy profile.

Overview

In this example we'll learn how to use the Bluetooth API of the BB10 platform to access the low-energy profile of Bluetooth devices.

The business logic is encapsulated in the BluetoothGatt C++ class, which is exported to QML under the name '_bluetoothGatt'.

The UI

The UI of this sample application consists of the NavigationPane with a couple of Pages. The pages are created dynamically and pushed on the NavigationPane. When the user leaves the pages through the 'Back' button, the NavigationPane emits the popTransitionEnded() signal which is used to trigger some cleanup actions inside the BluetoothGatt object

    onPopTransitionEnded: {
        if (page.objectName == "ServicePage")
            _bluetoothGatt.disconnectServices()
        else if (page.objectName == "CharacteristicsPage")
            _bluetoothGatt.resetCharacteristicsList()
        else if (page.objectName == "CharacteristicsEditorPage")
            _bluetoothGatt.resetEditor()

        page.destroy()
    }

Additionally the NavigationPane contains two SystemToast objects that are bound to the 'errorMessage' property of the BluetoothGatt object and the 'errorMessage' property of the CharacteristicsEditor object. Whenever these two properties change, the SystemToasts show up on screen with the new error message as content.

    attachedObjects: [
        SystemToast {
            body: _bluetoothGatt.errorMessage

            onBodyChanged: show()
        },
        SystemToast {
            body: _bluetoothGatt.editor.errorMessage

            onBodyChanged: show()
        }
    ]

The main page

After startup the main page (implemented in main.qml) is shown, which contains a ListView with all paired, low-energy devices.

The 'Refresh' action of the page is placed on the ActionBar and triggers a rescanning of the available low-energy Bluetooth devices by calling the 'refreshDevices()' slot of the BluetoothGatt object.

    actions: [
        ActionItem {
            title: qsTr("Refresh")
            imageSource: "asset:///images/ic_refresh.png"
            onTriggered: {
                _bluetoothGatt.refreshDevices();
            }
            ActionBar.placement: ActionBarPlacement.OnBar
        }
    ]

The list of devices is then provided by the BluetoothGatt object through its 'devicesModel' property. This property is of type bb::cascades::DataModel, so it can be used directly as input for the ListView's 'dataModel' property.

    ListView {
        onTriggered: {
            if (indexPath.length != 0) {
                _bluetoothGatt.viewServices(indexPath[0]);
                navigationPane.push(servicesPage.createObject())
            }
        }

        dataModel: _bluetoothGatt.devicesModel

Whenever the user clicks on one of the items in the ListView, the Service Page for the selected device is shown. That is implemented inside the 'onTriggered' signal handler, where in the first step the BluetoothGatt object is informed about selected device and in a second step the Service Page is pushed on the NavigationPane.

The Service Page is declared as a ComponentDefinition.

    attachedObjects: [
        ComponentDefinition {
            id: servicesPage
            source: "Services.qml"
        }
    ]

The Services Page

The Service Page (implemented in Services.qml) contains a ListView with all the services that are provided by the currently selected device.

    ListView {
        dataModel: _bluetoothGatt.servicesModel

        onTriggered: {
            if (indexPath.length > 0) {
                _bluetoothGatt.viewCharacteristics(indexPath[0]);
                navigationPane.push(characteristicsPage.createObject())
            }
        }

        listItemComponents: [
            ListItemComponent {
                type: "item"
                Container {
                    topPadding: 10
                    leftPadding: 10
                    rightPadding: 10

                    Label {
                        text: ListItemData.uuid
                    }

                    Label {
                        text: ListItemData.name
                    }

                    Divider {
                    }
                }
            }
        ]
    }

The list of services is provided by the BluetoothGatt object through its 'servicesModel' property. This property is of type bb::cascades::DataModel, so it can be used directly as input for the ListView's 'dataModel' property. The UUID and name of the service is accessible through the 'ListItemData' object inside the ListItemComponent.

Whenever the user clicks on one of the items in the ListView, the Characteristics Page for the selected service is shown. That is implemented inside the 'onTriggered' signal handler, where in the first step the BluetoothGatt object is informed about selected service and in a second step the Characteristics Page is pushed on the NavigationPane.

The Characteristics Page is declared as a ComponentDefinition.

    attachedObjects: [
        ComponentDefinition {
            id: characteristicsPage
            source: "Characteristics.qml"
        }
    ]

The Characteristics Page

The Characteristics Page (implemented in Characteristics.qml) contains a ListView with all the characteristics that are provided by the currently selected service.

    ListView {
        dataModel: _bluetoothGatt.characteristicsModel

        onTriggered: {
            if (indexPath.length > 0) {
                _bluetoothGatt.viewCharacteristicsEditor(indexPath[0]);
                navigationPane.push(characteristicsEditorPage.createObject())
            }
        }

        listItemComponents: [
            ListItemComponent {
                type: "item"
                Container {
                    topPadding: 10
                    leftPadding: 10
                    rightPadding: 10

                    Label {
                        text: ListItemData.uuid
                    }

                    Label {
                        text: ListItemData.name
                    }

                    Divider {
                    }
                }
            }
        ]
    }

The list of characteristics is provided by the BluetoothGatt object through its 'characteristicsModel' property. This property is of type bb::cascades::DataModel, so it can be used directly as input for the ListView's 'dataModel' property. The UUID and name of the characteristic is accessible through the 'ListItemData' object inside the ListItemComponent.

Whenever the user clicks on one of the items in the ListView, the Characteristics Editor Page for the selected characteristic is shown. That is implemented inside the 'onTriggered' signal handler, where in the first step the BluetoothGatt object is informed about selected characteristic and in a second step the Characteristics Editor Page is pushed on the NavigationPane.

The Characteristics Editor Page is declared as a ComponentDefinition.

    attachedObjects: [
        ComponentDefinition {
            id: characteristicsEditorPage
            source: "CharacteristicsEditor.qml"
        }
    ]

The Characteristics Editor Page

The Characteristics Editor Page (implemented in CharacteristicsEditor.qml) allows the user to view and modify values of the selected characteristic. At the top of the page the UUID, name and flags are shown. For this purpose a custom control LabelLabel (implemented in LabelLabel.qml) has been defined, which simply shows two Labels next to each other.

The values for the UUID, name and flags are provided by the CharacteristicsEditor C++ object, which is accessible through the 'editor' property of the BluetoothGatt object.

    LabelLabel {
        title: _bluetoothGatt.editor.characteristicUUID
        text: _bluetoothGatt.editor.characteristicHandle
    }

    LabelLabel {
        title: _bluetoothGatt.editor.characteristicName
        text: _bluetoothGatt.editor.characteristicValueHandle
    }

The value of the characteristic can be modified inside a TextField below the Labels. To read and modify the value, the 'characteristicsValue' property of the CharacteristicsEditor is used.

    TextField {
        text: _bluetoothGatt.editor.characteristicValue
        layoutProperties: StackLayoutProperties {
            spaceQuota: 10
        }
        id: characteristicValue
        onTextChanged: {
            _bluetoothGatt.editor.setCharacteristicValue( text )
        }
    }

Next to the TextField an ImageView is located that provides three context actions to read and write the characteristic value. The user has to long-press on the image to bring up the context menu.

    ImageView {
        imageSource: "asset:///images/update.png"
        contextActions: ActionSet {
            ActionItem {
                title: qsTr("Read")
                imageSource: "asset:///images/ic_refresh.png"
                enabled: _bluetoothGatt.editor.readCharacteristicValueAllowed
                onTriggered: {
                    _bluetoothGatt.editor.readCharacteristicValue()
                }
            }
            ActionItem {
                title: qsTr("Write")
                imageSource: "asset:///images/ic_write.png"
                enabled: _bluetoothGatt.editor.writeCharacteristicValueAllowed
                onTriggered: {
                    if (characteristicValueText.focused )
                        _bluetoothGatt.editor.setCharacteristicValueText( characteristicValueText.text )
                    else _bluetoothGatt.editor.setCharacteristicValue( characteristicValue.text )
                    _bluetoothGatt.editor.writeCharacteristicValue(true)
                }
            }
            ActionItem {
                title: qsTr("Write (without response)")
                imageSource: "asset:///images/ic_write.png"
                enabled: _bluetoothGatt.editor.writeCharacteristicValueAllowed
                onTriggered: {
                    if (characteristicValueText.focused )
                        _bluetoothGatt.editor.setCharacteristicValueText( characteristicValueText.text )
                    else _bluetoothGatt.editor.setCharacteristicValue( characteristicValue.text )
                    _bluetoothGatt.editor.writeCharacteristicValue(false);
                }
            }
        }
    }

In the middle of the page two ToggleButtons are located to enable or disable the notifications and indicators of the characteristic. Depending on the status properties of the CharacteristicsEditor object, the ToggleButtons are enabled/disabled.

    ToggleButton {
        layoutProperties: StackLayoutProperties {
            spaceQuota: 1
        }
        checked: _bluetoothGatt.editor.characteristicNotificationsEnabled
        enabled: _bluetoothGatt.editor.characteristicNotificationsAllowed
        onCheckedChanged: {
            _bluetoothGatt.editor.characteristicNotificationsEnabled = checked
        }
    }

The ListView at the bottom of the page displays the descriptors of the selected characteristic. The entries are provided by a property of the CharacteristicsEditor object again and through the two slots readCharacteristicDescriptor() and writeCharacteristicDescriptor(), the entries can even be modified.

    ListView {
        dataModel: _bluetoothGatt.editor.descriptorsModel

        listItemComponents: [
            ListItemComponent {
                type: "item"

                Container {
                    id: listItemContainer

                    property int indexInSection: ListItem.indexInSection

                    leftPadding: 10
                    rightPadding: 10

                    background: ListItem.indexInSection % 2 == 1 ? Color.White : Color.LightGray

                    Label {
                        text: qsTr("%1 / %2").arg(ListItemData.uuid).arg(ListItemData.name)
                    }

                    Container {
                        layout: StackLayout {
                            orientation: LayoutOrientation.LeftToRight
                        }
                        TextField {
                            id: textField
                            layoutProperties: StackLayoutProperties {
                                spaceQuota: 10
                            }
                            text: ListItemData.value
                        }
                        ImageView {
                            imageSource: "asset:///images/update.png"
                            contextActions: ActionSet {
                                ActionItem {
                                    title: qsTr("Read")
                                    imageSource: "asset:///images/ic_refresh.png"
                                    onTriggered: {
                                        _bluetoothGatt.editor.readCharacteristicDescriptor(listItemContainer.indexInSection);
                                    }
                                }
                                ActionItem {
                                    title: qsTr("Write")
                                    imageSource: "asset:///images/ic_write.png"
                                    onTriggered: {
                                        _bluetoothGatt.editor.writeCharacteristicDescriptor(listItemContainer.indexInSection,textField.text);
                                    }
                                }
                            }
                        }
                    }
                    Container {
                        layout: StackLayout {
                            orientation: LayoutOrientation.LeftToRight
                        }
                        TextField {
                            id: textFieldAscii
                            layoutProperties: StackLayoutProperties {
                                spaceQuota: 10
                            }
                            text: ListItemData.valueAscii
                            enabled: false
                        }
                    }

                    Divider {
                    }
                }
            }
        ]
    }

The BluetoothGatt Object

The BluetoothGatt class encapsulates the business logic of this application. It uses the low-level Bluetooth API of the BB10 framework to list the available devices with low-energy profile and retrieves their provided services and characteristics.

The list of devices, services and characteristics are made available through properties, so that the ListViews in the UI can use them directly. Additionally it provides properties to access the currently selected device, the error message and the CharacteristicsEditor object. The latter is used to modify the characteristics of a service.

    class BluetoothGatt : public QObject
    {
        Q_OBJECT

        Q_PROPERTY(QString activeDevice READ activeDevice WRITE setActiveDevice NOTIFY activeDeviceChanged)
        Q_PROPERTY(QString activeService READ activeService WRITE setActiveService NOTIFY activeServiceChanged)
        Q_PROPERTY(QString activeCharacteristic READ activeCharacteristic WRITE setActiveCharacteristic NOTIFY activeCharacteristicChanged)

        Q_PROPERTY(bb::cascades::DataModel* devicesModel READ devicesModel CONSTANT)
        Q_PROPERTY(bb::cascades::DataModel* servicesModel READ servicesModel CONSTANT)
        Q_PROPERTY(bb::cascades::DataModel* characteristicsModel READ characteristicsModel CONSTANT)

        Q_PROPERTY(CharacteristicsEditor* editor READ editor CONSTANT)
        Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged)

    public:
        BluetoothGatt(QObject *parent = 0);
        virtual ~BluetoothGatt();

        // called by callbacks from the bluetooth library
        void gattServiceConnected(const QString &bdaddr, const QString &service, int instance, int err, uint16_t connInt, uint16_t latency, uint16_t superTimeout );
        void processBluetoothEvent(const int event, void *data);

    Q_SIGNALS:
        // The change notification signals of the properties
        void activeDeviceChanged();
        void activeServiceChanged();
        void activeCharacteristicChanged();
        void errorMessageChanged();

        void bluetoothEvent(const int event, void *data);

    public slots:
        void viewCharacteristics(int row);
        void viewCharacteristicsEditor(int row);
        void viewServices(int row);
        void refreshDevices();

        void disconnectServices();
        void resetEditor();
        void resetCharacteristicsList();
        void handleBluetoothEvent(const int event, void *data);
        void handleCharacteristicNotificationsEnabledChanged(bool);

    private:
        // The accessor methods for the properties
        QString activeDevice() const;
        void setActiveDevice(const QString&);
        QString activeDeviceName() const;
        void setActiveDeviceName(const QString&);
        QString activeService() const;
        void setActiveService(const QString&);
        QString activeCharacteristic() const;
        void setActiveCharacteristic(const QString&);

        bb::cascades::DataModel* devicesModel() const;
        bb::cascades::DataModel* servicesModel() const;
        bb::cascades::DataModel* characteristicsModel() const;
        CharacteristicsEditor* editor() const;
        QString errorMessage() const;

        void setErrorMessage(const QString &errorMessage);

        void gattServiceDisconnected(const QString &deviceAddress, const QString &uuid, int instance, int reason);
        void gattNotification(int instance, uint16_t handle, const uint8_t *val, uint16_t len);

        QString m_activeDevice;
        QString m_activeDeviceName;
        QString m_activeService;
        QString m_activeCharacteristic;

        bb::cascades::ArrayDataModel *m_devices;
        bb::cascades::ArrayDataModel *m_services;
        bb::cascades::ArrayDataModel *m_characteristics;

        bt_gatt_callbacks_t m_gatt_cb;
        int m_gattInstance;
        bt_gatt_characteristic_t *m_characteristicList;
        bt_gatt_characteristic_t *m_characteristicStruct;
        int m_characteristicListSize;

        CharacteristicsEditor *m_editor;
        QString m_errorMessage;
    };

Inside the constructor of BluetoothGatt, the models and the editor are initialized. Afterwards the object is assigned to 's_globalBluetoothGatt', a global variable that is needed by callbacks of the low-level Bluetooth API to access the BluetoothGatt object. Since the CharacteristicsEditor object is provided as property, it must be registered to the QML runtime environment. In the next step the generic Bluetooth stack is initialized by calling 'bt_device_init()' with a callback method as parameter. Afterwards the GATT and LE specific parts of the Bluetooth stack are initialized by calling 'bt_gatt_init()' and 'bt_le_init()'. The former takes a structure as parameter, which contains pointers to callbacks. These callbacks are executed whenever a connection to a GATT service has been established or terminated. In a last step refreshDevices() is called to load the list of paired low-energy devices.

    BluetoothGatt::BluetoothGatt(QObject *parent)
        : QObject(parent)
        , m_devices(new TypedArrayDataModel())
        , m_services(new TypedArrayDataModel())
        , m_characteristics(new TypedArrayDataModel())
        , m_characteristicList(0)
        , m_characteristicStruct(0)
        , m_editor(new CharacteristicsEditor(this))
    {
        s_globalBluetoothGatt = this;

        qmlRegisterType<CharacteristicsEditor>();

        m_devices->setParent(this);
        m_services->setParent(this);
        m_characteristics->setParent(this);

        // Initialize the btdevice and SPP library APIs.
        if (bt_device_init(bt_controller_cb) != EOK) {
            qWarning("Unable to initialize bluetooth device");
        }

        m_gatt_cb.connected = gatt_service_connected_cb;
        m_gatt_cb.disconnected = gatt_service_disconnected_cb;
        m_gatt_cb.updated = NULL;

        if (bt_gatt_init(&m_gatt_cb) != EOK) {
            /* TODO: Add error to UI and abort app */
            qWarning() << "GATT initialization failure" << errno;
        }

        if (bt_le_init(0) != EOK) {
            /* TODO: Add error to UI and abort app */
            qWarning() << "LE initialization failure " << errno;
        }

        bool ok = connect(this, SIGNAL(bluetoothEvent(const int, void*) ), this, SLOT(handleBluetoothEvent(const int, void *)));
        Q_ASSERT(ok);
        ok = connect(m_editor, SIGNAL(characteristicNotificationsEnabledChanged(bool)), this, SLOT(handleCharacteristicNotificationsEnabledChanged(bool)));
        Q_ASSERT(ok);

        refreshDevices();
    }

The destructor shuts down the GATT/LE specific and generic parts of the Bluetooth stack.

    BluetoothGatt::~BluetoothGatt()
    {
        // De-initialize the btdevice library.
        bt_device_deinit();

        bt_gatt_deinit();
        bt_le_deinit();

        s_globalBluetoothGatt = 0;
    }

The callback method, that has been passed to the 'bt_device_init()' call, is executed whenever an event is received from the Bluetooth stack. Inside the callback a copy of the event data is created and passed further to processBluetoothEvent() method.

    void bt_controller_cb(const int event, const char *bt_addr, const char *event_data)
    {
        btController_t *ctrl = (btController_t *)calloc(1, sizeof(btController_t));

        if (!ctrl)
            return;

        ctrl->event = event;
        ctrl->bt_addr = strdup(bt_addr);
        ctrl->event_data = strdup(event_data);

        if ((0 == ctrl->bt_addr) || (!processBluetoothEvent(BT_CONTROLLER_EVENT, (void*)ctrl))) {
            free(ctrl->bt_addr);
            free(ctrl->event_data);
            free(ctrl);
        }
    }

Inside processBluetoothEvent(), the global 's_globalBluetoothGatt' variable is used to invoke the processBluetoothEvent() member method of the BluetoothGatt object.

    bool processBluetoothEvent(const int event, void *data)
    {
        if (s_globalBluetoothGatt) {
            s_globalBluetoothGatt->processBluetoothEvent(event, data);
            return true;
        }
        return false;
    }

The member method itself invokes the handleBluetoothEvent() method, which demultiplexes the various events into different method invocations on the BluetoothGatt object.

    void BluetoothGatt::handleBluetoothEvent(const int event, void *data)
    {
        switch (event) {
        case BT_CONTROLLER_EVENT:
            if (data) {
                btController_t *ctrl = (btController_t*) data;
                switch (ctrl->event) {
                case BT_EVT_RADIO_SHUTDOWN:
                case BT_EVT_RADIO_INIT:
                    break;
                case BT_EVT_ACCESS_CHANGED:
                    break;
                case BT_EVT_DEVICE_ADDED:
                    qDebug() << "Device added:" << ctrl->bt_addr;
                    break;
                case BT_EVT_LE_DEVICE_CONNECTED:
                    qDebug() << "LE device connected:" << ctrl->bt_addr;
                    break;
                case BT_EVT_LE_DEVICE_DISCONNECTED:
                    qDebug() << "LE device disconnected:" << ctrl->bt_addr;
                    break;
                case BT_EVT_LE_NAME_UPDATED:
                    qDebug() << "LE device name updated:" << ctrl->bt_addr;
                    break;
                default:
                    qWarning() << "Unknown event" << ctrl->event << "/" << ctrl->bt_addr;
                    break;
                }

                free(ctrl->bt_addr);
                free(ctrl->event_data);
                free(ctrl);
            }
            break;
        case BT_GATT_CONNECT_EVENT:
            if (data) {
                btGatt_t *gatt = (btGatt_t*)data;
                gattServiceConnected(gatt->bdaddr, gatt->service, gatt->instance, gatt->err, gatt->connInt, gatt->latency, gatt->superTimeout);
                free(gatt->bdaddr);
                free(gatt->service);
                free(gatt);
            }
            break;
        case BT_GATT_DISCONNECT_EVENT:
            if (data) {
                btGatt_t *gatt = (btGatt_t*)data;
                gattServiceDisconnected(gatt->bdaddr, gatt->service, gatt->instance, gatt->err);
                free(gatt->bdaddr);
                free(gatt->service);
                free(gatt);
            }
            break;
        case BT_GATT_NOTIFICATION_EVENT:
            if (data) {
                btNotification_t *notify = (btNotification_t*)data;
                gattNotification(notify->instance, notify->handle, notify->val, notify->len);
                free(notify->val);
                free(notify);
            }
            break;
        default:
            break;
        }
    }

The two callbacks, that are passed to the 'bt_gatt_init()' call, use the same mechanism as described above to forward the received events to the handleBluetoothEvent() method.

    void gatt_service_connected_cb(const char *bdaddr, const char *service, int instance, int err, uint16_t connInt, uint16_t latency, uint16_t superTimeout, void *userData)
    {
        if ((0 == bdaddr) || (0 == service) || (0 == userData))
            return;

        btGatt_t *gatt = (btGatt_t *)calloc(1, sizeof(btGatt_t));

        if (!gatt)
            return;

        gatt->bdaddr = strdup(bdaddr);
        gatt->service = strdup(service);
        gatt->instance = instance;
        gatt->err = err;
        gatt->connInt = connInt;
        gatt->latency = latency;
        gatt->superTimeout = superTimeout;

        if ((0 == gatt->bdaddr) || (0 == gatt->service) || (!processBluetoothEvent(BT_GATT_CONNECT_EVENT, (void*) gatt))) {
            free(gatt->bdaddr);
            free(gatt->service);
            free(gatt);
        }
    }
    void gatt_service_disconnected_cb(const char *bdaddr, const char *service, int instance, int reason, void *userData)
    {
        if ((0 == bdaddr) || (0 == service) || (0 == userData))
            return;

        btGatt_t *gatt = (btGatt_t *) calloc(1, sizeof(btGatt_t));

        if (!gatt)
            return;

        gatt->bdaddr = strdup(bdaddr);
        gatt->service = strdup(service);
        gatt->instance = instance;
        gatt->err = reason;

        if ((0 == gatt->bdaddr) || (0 == gatt->service) || (!processBluetoothEvent(BT_GATT_DISCONNECT_EVENT, (void*) gatt))) {
            free(gatt->bdaddr);
            free(gatt->service);
            free(gatt);
        }
    }

On startup or whenever the user triggers the 'Refresh' action on the main page, the refreshDevices() method is invoked. Inside this method the model that stores the device information is cleared and afterwards the low-level Bluetooth API is used to retrieve all paired devices that provide a low-energy profile. For each found device, an entry is added to the model.

    void BluetoothGatt::refreshDevices()
    {
        m_devices->clear();

        bt_remote_device_t **remote_device_array;
        char tempbuff[128];
        bt_remote_device_t *next_remote_device = 0;
        char **services_array = 0;

        // Retrieve and show all paired devices.
        remote_device_array = bt_disc_retrieve_devices(BT_DISCOVERY_ALL, 0);
        if (remote_device_array) {
            for (int i = 0; 0 != (next_remote_device = remote_device_array[i]); ++i) {
                    services_array = bt_rdev_get_services_gatt( next_remote_device );
                    if (services_array ) {
                            bt_rdev_free_services( services_array );

                    QVariantMap map;
                    map["type"] = "item";
                    bt_rdev_get_friendly_name(next_remote_device, tempbuff, 128);
                    map["name"] = tempbuff;
                    bt_rdev_get_addr(next_remote_device, tempbuff);
                    map["address"] = tempbuff;
                    map["paired"] = true;
                    m_devices->append(map);
                }
            }
            bt_rdev_free_array(remote_device_array);
        }
    }

Whenever the user selects a device in the UI, the viewServices() method is invoked. After a sanity check on the model index, the entry for the selected device is retrieved from the model and the 'activeDevice' property is updated. In the next step the model that contains the service information is cleared and the low-level Bluetooth API is used to retrieve the handle for the device with the given MAC address.

If a valid handle is returned, we iterate over all low-energy services, add an entry for each of them to the model and connect against the service by calling 'bt_gatt_connect_service()'.

    void BluetoothGatt::viewServices(int which)
    {
        if (which < 0 || which >= m_devices->size())
            return;

        const QVariantMap device = m_devices->value(which).toMap();
        if (device.isEmpty())
            return;

        setActiveDevice(device["address"].toString());
        setActiveDeviceName(device["name"].toString());

        m_services->clear();

        bt_remote_device_t *remote_device = bt_rdev_get_device(device["address"].toString().toAscii());

        if (!remote_device)
            return;

        int device_type = 0;
        char **services_array = 0;

        /**
         * NOTE:  For low energy devices, you should NOT perform a bt_rdev_refresh_services() here,
         *      as the service advertisements may have dropped since pairing.
         */

        //  Display all known basic device information.
        device_type = bt_rdev_get_type(remote_device);

        //  Display any found Bluetooth low energy services and connect to each.
        if (device_type == BT_DEVICE_TYPE_LE_PUBLIC || device_type == BT_DEVICE_TYPE_LE_PRIVATE) {
            if ((services_array = bt_rdev_get_services_gatt(remote_device))) {
                int i;
                bt_gatt_conn_parm_t conParm;
                for (i = 0; 0 != services_array[i]; i++) {
                    QVariantMap map;
                    if (strncasecmp(services_array[i], "0x", 2) == 0 ){
                        map["uuid"] = &(services_array[i][2]);
                    } else {
                        map["uuid"] = services_array[i];
                    }
                    int got_svc_name=0;
                    if ( strcmp(services_array[i],"0xF000AA00-0451-4000-B000-000000000000") == 0) {
                            map["name"] = "TI IR Temperature Service";
                            got_svc_name = 1;
                    }
                    if ( strcmp(services_array[i],"0xF000AA10-0451-4000-B000-000000000000") == 0) {
                            map["name"] = "TI Accelerometer Service";
                            got_svc_name = 1;
                    }
                    if ( strcmp(services_array[i],"0xF000AA20-0451-4000-B000-000000000000") == 0) {
                            map["name"] = "TI Humidity Service";
                            got_svc_name = 1;
                    }
                    if ( strcmp(services_array[i],"0xF000AA30-0451-4000-B000-000000000000") == 0) {
                            map["name"] = "TI Magnetometer Service";
                            got_svc_name = 1;
                    }
                    if ( strcmp(services_array[i],"0xF000AA40-0451-4000-B000-000000000000") == 0) {
                            map["name"] = "TI Barometer Service";
                            got_svc_name = 1;
                    }
                    if ( strcmp(services_array[i],"0xF000AA50-0451-4000-B000-000000000000") == 0) {
                            map["name"] = "TI Gyroscope Service";
                            got_svc_name = 1;
                    }
                    if ( strcmp(services_array[i],"0xF000AA60-0451-4000-B000-000000000000") == 0) {
                            map["name"] = "TI Test Service";
                            got_svc_name = 1;
                    }
                    if (got_svc_name == 0) {
                            map["name"] = Util::parse_service_uuid( services_array[i] );
                    }
                    map["connected"] = false;
                    m_services->append(map);

                    conParm.minConn = 0x30;
                    conParm.maxConn = 0x50;
                    conParm.latency = 0;
                    conParm.superTimeout = 50;
                    qDebug() << "Connecting service" << QString(services_array[i]);
                    if (bt_gatt_connect_service(activeDevice().toAscii().constData(), services_array[i], 0, &conParm, this) < 0) {
                        setErrorMessage(QString("GATT connect service request failed: %1 (%2)").arg(errno).arg(strerror(errno)));
                    }
                }

                bt_rdev_free_services(services_array);
            }
        }

        bt_rdev_free(remote_device);
    }

Whenever the user selects a service in the ListView on the Services Page, the viewCharacteristics() method is invoked. After a sanity check on the model index, the entry for the selected service is retrieved from the model and the 'activeService' property is updated. In the following steps the low-level Bluetooth API is used to retrieve the list of characteristics for the selected service and to fill the characteristics model with entries.

    void BluetoothGatt::viewCharacteristics(int row)
    {
        if (row < 0 || row >= m_services->size())
            return;

        const QVariantMap service = m_services->value(row).toMap();

        m_characteristics->clear();

        if (!service["connected"].toBool()) {
            setErrorMessage(QString("%1: service not connected: %2").arg(activeDeviceName()).arg(service["uuid"].toString()));
            return;
        }

        setActiveService(service["uuid"].toString());

        m_gattInstance = service["instance"].toInt();

        if (m_characteristicList) {
            free(m_characteristicList);
            m_characteristicList = 0;
        }
        m_characteristicListSize = bt_gatt_characteristics_count(m_gattInstance);
        if (m_characteristicListSize == -1) {
            qWarning() << "GATT characteristics count failed:" << errno << "(" << strerror(errno) << ")";
            bt_gatt_disconnect_instance(m_gattInstance);
            return;
        }

        if (m_characteristicListSize == 0) {
            setErrorMessage("GATT Characteristic count returned 0");
            bt_gatt_disconnect_instance(m_gattInstance);
            return;
        }

        m_characteristicList = (bt_gatt_characteristic_t*)malloc(m_characteristicListSize * sizeof(bt_gatt_characteristic_t));
        if (!m_characteristicList) {
            setErrorMessage("GATT characteristics: Not enough memory");
            bt_gatt_disconnect_instance(m_gattInstance);
            return;
        }

        /* BEGIN WORKAROUND - Temporary fix to address race condition */
        int number = 0;
        do {
            number = bt_gatt_characteristics(m_gattInstance, m_characteristicList, m_characteristicListSize);

        } while ((number == -1) && (errno== EBUSY));

        m_characteristicListSize = number;
        /* END WORKAROUND */

        if (m_characteristicListSize == -1) {
            setErrorMessage(QString("GATT characteristics failed: %1 (%2)").arg(errno).arg(strerror(errno)));
            bt_gatt_disconnect_instance(m_gattInstance);
            return;
        }

        qDebug() << "GATT characteristics: Retreived" << m_characteristicListSize << "successfully";

        /**
         *  Get the characteristics of the GATT service here and add them to the model.
         */
        for (int i = 0; i < m_characteristicListSize; i++) {
            QVariantMap map;
            map["name"] = Util::parse_characteristic_uuid(m_characteristicList[i].uuid);
            map["uuid"] = m_characteristicList[i].uuid;
            qDebug() << "Properties" << m_characteristicList[i].properties;

            m_characteristics->append(map);
        }

        bt_gatt_reg_notifications(m_gattInstance, notifications_cb);
    }

In a last step the notifications_cb() callback method is registered to be informed about notifications for this service that come from the GATT stack.

    static void notifications_cb(int instance, uint16_t handle, const uint8_t *val, uint16_t len, void*)
    {
        qDebug() << "Notification callback";
        if ( 0 == val )
            return;

        btNotification_t *notify = (btNotification_t *)calloc(1, sizeof(btNotification_t));

        if (0 == notify)
            return;

        notify->instance = instance;
        notify->handle = handle;
        notify->val = (uint8_t*)calloc(len, sizeof(uint8_t));
        if (0 == notify->val) {
            free(notify);
            return;
        }
        memcpy(notify->val, val, len);
        notify->len = len;
        if ((0 == notify->val) || (!processBluetoothEvent(BT_GATT_NOTIFICATION_EVENT, (void*) notify))) {
            free(notify->val);
            free(notify);
        }
    }

Whenever the user selects a characteristic in the ListView on the Characteristics Page, the viewCharacteristicsEditor() method is invoked. After a sanity check on the index, the struct, that describes the characteristic, is retrieved from the list of available characteristics and set on the CharacteristicsEditor object together with the identifier of the active GATT instance. These information are sufficient for the CharacteristicsEditor to display and modify the various values of the characteristic.

    void BluetoothGatt::viewCharacteristicsEditor(int row)
    {
        if (row < 0 || row >= m_characteristicListSize)
            return;

        m_characteristicStruct = &(m_characteristicList[row]);

        // update characteristic editor
        m_editor->setGattInstance(m_gattInstance);
        m_editor->setCharacteristic(m_characteristicStruct);
    }

Whenever the GATT specific callbacks are invoked by the Bluetooth stack to signal that a connection to a service has been established/terminated, the gattServiceConnected() and gattServiceDisconnected() methods are executed. These two methods extract the service UUID from the event parameters, look up the corresponding entry in the services model and modify the 'connected' property of the entry accordingly.

    void BluetoothGatt::gattServiceConnected(const QString&, const QString &_serviceUuid, int instance, int err, uint16_t, uint16_t, uint16_t)
    {
        if (err != EOK) {
            setErrorMessage(QString("GATT service connection failed %1 (%2)").arg(err).arg(strerror(err)));
            return;
        }

        QString serviceUuid(_serviceUuid);
        if (serviceUuid.startsWith("0x"))
            serviceUuid = serviceUuid.mid(2);

        for (int i = 0; i < m_services->size(); i++) {
            QVariantMap service = m_services->value(i).toMap();
            if (serviceUuid == service["uuid"].toString()) {
                service["connected"] = true;
                service["instance"] = instance;
                m_services->replace(i, service);
                qDebug() << "GATT service connected:" << serviceUuid;
                break;
            }
        }
    }
    void BluetoothGatt::gattServiceDisconnected(const QString&, const QString &_serviceUuid, int, int err )
    {
        if (err != EOK) {
            setErrorMessage(QString("GATT service disconnection failed %1 (%2)").arg(err).arg(strerror(err)));
            return;
        }

        QString serviceUuid(_serviceUuid);
        if (serviceUuid.startsWith("0x"))
            serviceUuid = serviceUuid.mid(2);

        for (int i = 0; i < m_services->size(); i++) {
            QVariantMap service = m_services->value(i).toMap();
            if (serviceUuid == service["uuid"].toString()) {
                service["connected"] = false;
                m_services->replace(i, service);
                qDebug() << "GATT service disconnected:" << serviceUuid;
                break;
            }
        }
    }

If the registered callback for retrieving notifications from the GATT stack is invoked by the Bluetooth stack, the gattNotification() method is executed, which forwards the new values to the CharacteristicsEditor object, so that it always works on the current value.

    void BluetoothGatt::gattNotification(int instance, uint16_t handle, const uint8_t *val, uint16_t len)
    {
        if (instance == m_gattInstance && m_characteristicStruct && handle == m_characteristicStruct->value_handle) {
            qDebug("Bluetooth Gatt Sample:\tCharacteristic notification:  val @%lx, len %d.\n", (long)val, len);

            m_editor->setCharacteristicValue(val, len);
        } else {
            qDebug("Bluetooth Gatt Sample:\tNon-characteristic notification:  instance %d, handle %d, val @%lx, len %d.\n", instance, handle, (long)val, len);
        }
    }

To inform the user about an error, the BluetoothGatt object provides an 'errorMessage' property, which is bound against a SystemToast object in the UI. So whenever some business logic code calls the setErrorMessage() method, the 'errorMessage' property is updated and the UI will show the SystemToast.

    void BluetoothGatt::setErrorMessage(const QString &errorMessage)
    {
        m_errorMessage = errorMessage;
        emit errorMessageChanged();
    }

When the user leaves the Characteristics Editor Page, the Characteristics Page or the Services Page, some cleanup actions must be executed. This is implemented by listening to the popTransitionEnded() signal of the NavigationPane and check against the 'objectName' property of the popped page.

If the Characteristics Editor Page is popped, the resetEditor() slot is invoked, which simply clears the structure for the selected characteristic.

    void BluetoothGatt::resetEditor()
    {
        m_characteristicStruct = 0;
    }

If the Characteristics Page is popped, the resetCharacteristicsList() slot is invoked, which clears the list of characteristic objects.

    void BluetoothGatt::resetCharacteristicsList()
    {
        if (m_characteristicList) {
            free(m_characteristicList);
            m_characteristicList = 0;
        }
    }

If the Services Page is popped, the disconnectServices() slot is invoked, which terminates the connections for all services.

    void BluetoothGatt::disconnectServices()
    {
        for (int i = 0; i < m_services->size(); i++) {
            const QVariantMap service = m_services->value(i).toMap();
            const int rc = bt_gatt_disconnect_service(activeDevice().toAscii().constData(), service["uuid"].toString().toAscii().constData());
            gattServiceDisconnected(activeDevice(), service["uuid"].toString(), 0, rc);
        }
    }