0

I have a problem with reading characteristic using Bluetooth Low Energy Qt api. The device I'm communicating with is a Decawave DWM1001 module. I was following the tutorial from the documentation. I managed to connect to the device, read and creates it's service successfully. The device has "network node service" with UUID: 680c21d9-c946-4c1f-9c11-baa1c21329e7, which I'm trying to read characteristics from. I call QLowEnergyController::connectToDevice() method, it finds the service and I create a QLowEnergyService object for it, named nodeService.

void BluetoothConnector::serviceDiscovered(const QBluetoothUuid &newService)
{
    qDebug() << "Service discovered: " << newService.toString();

    if (newService == QBluetoothUuid(nodeServiceUUID)) {
        nodeService = controller->createServiceObject(QBluetoothUuid(nodeServiceUUID), this);

        if (nodeService) {
            qDebug() << "Node service created";
            connect(nodeService, &QLowEnergyService::stateChanged, this, &BluetoothConnector::serviceStateChanged);
            connect(nodeService, &QLowEnergyService::characteristicChanged, this, &BluetoothConnector::updateCharacteristic);
            //connect(nodeService, &QLowEnergyService::descriptorWritten, this, &BLTest::confirmedDescriptorWrite);
            nodeService->discoverDetails();
        } else {
            qDebug() << "Node service not found.";
        }
    }
}

nodeService is created successfully (I get "Node service created" log), then I connect the signals and slots for the service and then call discoverDetails() on nodeService. The serviceStateChanged() slot looks like this:

void BluetoothConnector::serviceStateChanged(QLowEnergyService::ServiceState newState)
{
    if (newState == QLowEnergyService::DiscoveringServices) {
        qDebug() << "Discovering services";

    } else if (newState == QLowEnergyService::ServiceDiscovered) {
        qDebug() << "Service discovered";

        const QLowEnergyCharacteristic networkIdChar = nodeService->characteristic(QBluetoothUuid(networkIdUUID));
        const QLowEnergyCharacteristic dataModeChar = nodeService->characteristic(QBluetoothUuid(dataModeUUID));
        const QLowEnergyCharacteristic locationChar = nodeService->characteristic(QBluetoothUuid(locationUUID));

        if (networkIdChar.isValid() && dataModeChar.isValid() && locationChar.isValid()) {
            auto idValue = networkIdChar.value();
            auto modeValue = dataModeChar.value();
            auto locValue = locationChar.value();

            qDebug() << "Network ID: " << idValue;
            qDebug() << "Mode: " << modeValue;
            qDebug() << "Location: " << locValue;

            auto notificationDesc = locationChar.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
            if (notificationDesc.isValid()) {
                qDebug() << "Notification desc valid";
                nodeService->writeDescriptor(notificationDesc, QByteArray::fromHex("0100"));
            }
        } else {
            qDebug() << "Characteristic invalid";
        }
    }
}

I get the "Discovering services" log and after that the app hangs for a bit and then I get:

"Cannot read descriptor (onDescReadFinished 3):  \"{00002902-0000-1000-8000-00805f9b34fb}\" \"{3f0afd88-7770-46b0-b5e7-9fc099598964}\" \"org.freedesktop.DBus.Error.NoReply\" \"Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.\""
"LowEnergy controller disconnected"
"Aborting onCharReadFinished due to disconnect"

It can't read the 00002902-0000-1000-8000-00805f9b34fb which is CCCD descriptor for the 3f0afd88-7770-46b0-b5e7-9fc099598964 characteristic (vendor specific). Can't figure out what's wrong and why it can't read characteristics from the device. Do I need to do something else to make it work?

Thanks.

---UPDATE---

BluetoothConnector class:

#include "bluetoothconnector.h"

#include <QTimer>

BluetoothConnector::BluetoothConnector(QObject *parent) : QObject(parent)
{
    configureDiscoveryAgent();
}

void BluetoothConnector::scan()
{
    agent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
}

void BluetoothConnector::connectToDevice(QString &addr)
{
    auto it = std::find_if(devices.begin(), devices.end(),
                           [&] (const QBluetoothDeviceInfo& d) { return d.address().toString() == addr; });

    if (it == devices.end())
        return;

    device = *it;
    controller = QLowEnergyController::createCentral(device, this);
    connect(controller, &QLowEnergyController::serviceDiscovered, this, &BluetoothConnector::serviceDiscovered);
    connect(controller, &QLowEnergyController::discoveryFinished, this, &BluetoothConnector::serviceScanDone);
    connect(controller, static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
            this, [this](QLowEnergyController::Error error) {
        Q_UNUSED(error);
        qDebug() << "Controller error: " << error;
        emit controllerError();
    });
    connect(controller, &QLowEnergyController::connected, this, [this]() {
        qDebug() << "Controller connected. Search services...";
        controller->discoverServices();
    });
    connect(controller, &QLowEnergyController::disconnected, this, [this]() {
        qDebug() << "LowEnergy controller disconnected";
    });
    controller->connectToDevice();
}

void BluetoothConnector::configureDiscoveryAgent()
{
    agent = new QBluetoothDeviceDiscoveryAgent(this);
    agent->setLowEnergyDiscoveryTimeout(5000);
    connect(agent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &BluetoothConnector::addDevice);
    connect(agent, static_cast<void (QBluetoothDeviceDiscoveryAgent::*)(QBluetoothDeviceDiscoveryAgent::Error)>(&QBluetoothDeviceDiscoveryAgent::error),
            this, &BluetoothConnector::scanError);

    connect(agent, &QBluetoothDeviceDiscoveryAgent::finished, this, &BluetoothConnector::scanFinished);
    connect(agent, &QBluetoothDeviceDiscoveryAgent::canceled, this, &BluetoothConnector::scanFinished);
}

void BluetoothConnector::addDevice(const QBluetoothDeviceInfo &info)
{
    if (!devices.contains(info)) {
        qDebug() << "Found device: " << info.name();
        devices.append(info);
        emit deviceFound(info);
    }
}

void BluetoothConnector::scanError(QBluetoothDeviceDiscoveryAgent::Error error)
{
    qDebug() << "Scan error: " << error;
}

void BluetoothConnector::scanFinished()
{
    emit scanFinishedSignal();
}

void BluetoothConnector::serviceDiscovered(const QBluetoothUuid &newService)
{
    qDebug() << "Service discovered: " << newService.toString();

    if (newService == QBluetoothUuid(nodeServiceUUID)) {
        nodeService = controller->createServiceObject(QBluetoothUuid(nodeServiceUUID), this);
        qDebug() << "State: " << nodeService->state();

        if (nodeService) {
            qDebug() << "Node service created";
            connect(nodeService, &QLowEnergyService::stateChanged, this, &BluetoothConnector::serviceStateChanged);
            connect(nodeService, &QLowEnergyService::characteristicChanged, this, &BluetoothConnector::updateCharacteristic);
            connect(nodeService, &QLowEnergyService::characteristicWritten, this, &BluetoothConnector::characteristicWritten);
            connect(nodeService, QOverload<QLowEnergyService::ServiceError>::of(&QLowEnergyService::error),
                    [=](QLowEnergyService::ServiceError newError){ qDebug() << newError; });

            //connect(nodeService, &QLowEnergyService::descriptorWritten, this, &BLTest::confirmedDescriptorWrite);
            nodeService->discoverDetails();
        } else {
            qDebug() << "Node service not found.";
        }
    }
}

void BluetoothConnector::serviceScanDone()
{
    qDebug() << "Services scan done";
}

void BluetoothConnector::characteristicWritten(const QLowEnergyCharacteristic &info, const QByteArray &value)
{
    qDebug() << "Characteristic written: " << info.name();
}

void BluetoothConnector::serviceStateChanged(QLowEnergyService::ServiceState newState)
{
    qDebug() << "State changed: " << newState;

    if (newState == QLowEnergyService::ServiceDiscovered) {
        qDebug() << "Service discovered";

        const QLowEnergyCharacteristic networkIdChar = nodeService->characteristic(QBluetoothUuid(networkIdUUID));
        const QLowEnergyCharacteristic dataModeChar = nodeService->characteristic(QBluetoothUuid(dataModeUUID));
        const QLowEnergyCharacteristic locationChar = nodeService->characteristic(QBluetoothUuid(locationUUID));

        if (networkIdChar.isValid() && dataModeChar.isValid() && locationChar.isValid()) {
            auto idValue = networkIdChar.value();
            auto modeValue = dataModeChar.value();
            auto locValue = locationChar.value();

            qDebug() << "Network ID: " << idValue;
            qDebug() << "Mode: " << modeValue;
            qDebug() << "Location: " << locValue;

            auto notificationDesc = locationChar.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
            if (notificationDesc.isValid()) {
                qDebug() << "Notification desc valid";
                nodeService->writeDescriptor(notificationDesc, QByteArray::fromHex("0100"));
            }
        } else {
            qDebug() << "Characteristic invalid";
        }
    }
}

void BluetoothConnector::updateCharacteristic(const QLowEnergyCharacteristic &info, const QByteArray &value)
{
    if (info.uuid() == QBluetoothUuid(networkIdUUID)) {
        qDebug() << "Update ID: " << value;
    } else if (info.uuid() == QBluetoothUuid(dataModeUUID)) {
        qDebug() << "Update mode: " << value;
    } else if (info.uuid() == QBluetoothUuid(locationUUID)) {
        qDebug() << "Update location: " << value;
    }
}
Piotr
  • 71
  • 12
  • I was having the same problem....found a solution: https://stackoverflow.com/questions/71040775/qt-qtlowenergyservice-discoverdetails-does-not-discover-non-standard-chara/71052226#71052226 – bambamAz Feb 09 '22 at 15:30

1 Answers1

0

I think, the problem is located on the remote side (your GATT server). Check the QLowEnergyService::error() and QLowEnergyService::charcteristicWritten() signal and see if you get any response. What kind of Bluetooth stack you are use on the remote side? Might be, you don't have the rights to change the CCCD due to a implementation failure. Please check the implementation on the remote side.

AD1170
  • 378
  • 1
  • 9
  • Thanks for the response. I added slots for error() and characteristicWritten() signals but they are not called. I don't have access to the implementation on the remote device. It's a RTLS module: Decawave DWM1001. Not sure about bluetooth stack, I'm using official api documentation from the Decawave site to implement my client app. Link to documentation: https://www.decawave.com/dwm1001/api/, (chapter 7: BLE api). And one important thing: I'm sure the problem is with my app because I tested the example lowenergyscanner Qt app and it can read and list all the characteristic from the device. – Piotr Apr 09 '21 at 10:00
  • OK, what is the state of the nodeService after the service was created? nodeServce->state() == QLowEnergyService::DiscoveryRequired or somthing else? – AD1170 Apr 09 '21 at 10:31
  • Yes, the state is DiscoveryRequired. – Piotr Apr 09 '21 at 11:40
  • It looks like the connection is terminated during the service discovery. Can you check if the serviceStateChaned() slot is called at all. – AD1170 Apr 09 '21 at 12:44
  • Yes, it's called three times with: DiscoveryRequired, DiscoveringServices, Invalid Service (when disconnecting). I think the problem is that it is not a well-know gatt profile, in the lowenergyscanner example, it displays the service name as "Unknown", maybe Qt can't read characteristics from custom services. – Piotr Apr 09 '21 at 12:56
  • No, that doesn't matter. I have already implemented a lot of Qt BLE applications, even with custom services. Is it possible to see your whole code, at least the Bluetooth part? – AD1170 Apr 09 '21 at 13:01
  • Right now I don't have access to it, I will show it on Monday. One more thing about the lowenergyscanner example app. I did some debugging with it and it also doesn't get the ServiceDiscovered state, it goes from DiscoveryRequired, DiscoveringServices, Invalid Service, like in my app, then gets disconnected with the "Cannot read descriptor" error, but it shows the characteristics from the device, their UUIDs, and their values. All characteristics are labeled as "Unknown" thought and I'm wondering how it can read the characteristic values without discovering the service. – Piotr Apr 10 '21 at 14:19
  • Read Characteristics and their values is one thing, that can be done w/o deep discovery. But for writing to characteristics to enable indication or notification, you must have the permission. I assure you run your app under Linux, right? Try to run your app as root. – AD1170 Apr 11 '21 at 18:25
  • Running as root doesn't change anything. I added my bluetooth class. – Piotr Apr 12 '21 at 05:20
  • Do you know the Nordic Semiconductor mobile app "nRF Connect"? The app is available on Playstore / Appstore for Android and iOS. You can try to connect to your device and explore the available services. Furthermore you can try to read characteristics and enable notification/indication. If this work, perhaps you can try to run your app an Android to see if it works. – AD1170 Apr 12 '21 at 06:40
  • I downloaded the nRF app and it finds all the characteristics from the device. I was even able to enable the notification of one of its characteristics and it works as expected (I know for sure because if nothing is written to the device for 10 seconds after connected it should disconnect). The nRF app displays all the characteristics as "Unknown Characteristic". I tried to do the same in my app, despite the lack of "Service discovered" state, because I noticed that service does find the characteristics (characteristics() methods returns all of them) but when trying to write something – Piotr Apr 12 '21 at 07:42
  • I automatically get QLowEnergyService::OperationError because as the documentation states: "A descriptor can only be written if this service is in the ServiceDiscovered state, belongs to the service. If one of these conditions is not true the QLowEnergyService::OperationError is set." Is there any workaround for this, so I can write to the device anyway without the "Service discovered" state? – Piotr Apr 12 '21 at 07:44
  • This due to the fact that the characteristic (r/w) permission is retrieved during the service details discovery. There is no work around (afaik). Are you able to test the app under Android? I still think this can be an issue with the bluez Bluetooth stack or OS related. – AD1170 Apr 12 '21 at 08:01
  • Ok, I'll try to install my app on the Android device and see if it's working. – Piotr Apr 12 '21 at 08:45
  • Installed my app on the android device and it works, I get the "Service discovered" state. I have no more ideas of what to do to get it working on the Raspberry Pi 4 (Raspberry Pi OS). – Piotr Apr 12 '21 at 10:09
  • My Raspi Bluetooth LE application works only when i start the application as root (not as user "pi"). Even though, i have added the permissions and file capabilities (CAP_NET_RAW and CAP_NET_ADMIN). You can also try to enable the blueZ log / debug messages, perhaps you find out whats going wrong (see https://stackoverflow.com/questions/37003147/i-want-to-enable-debug-messages-on-bluez). But for me, it was OK to run the application as root. – AD1170 Apr 12 '21 at 12:27
  • I can't get it to work, I tried running as root, adding CAP_NET_RAW and CAP_NET_ADMIN, tried on desktop Ubuntu and on Raspberry Pi OS. The vendor service didn't get the ServiceDiscovered state no matter what I tried, but the other generic service (0x1801) gets it. I can connect, read and write to characteristics by bluetoothctl tool and I even tried a simple Python app and it works as well. I have no idea, what else I can do. – Piotr Apr 13 '21 at 09:12
  • Very strange. Perhaps, this is a bug in the blueZ. I have read that some users have trouble with the pi4 (indeed Ubuntu 20.02, https://askubuntu.com/questions/1289623/bluetooth-problem-20-10-on-raspberry-pi-4). You can try to update the blueZ to a later version (http://blog.wenzlaff.de/?p=15054) but not sure if this help. What about the blueZ debugging? Has you try it? – AD1170 Apr 13 '21 at 21:19
  • I tried debugging and can't see any errors or anything like that, all the characteristic seems to be found correctly except maybe 2 lines: Unsupported characteristic: 00002aa6-0000-1000-8000-00805f9b34fb and Unsupported characteristic: 00002a04-0000-1000-8000-00805f9b34fb. It seems to me that the problem is more Qt related because every other method of connecting to my ble device works. I'll try update the blueZ. – Piotr Apr 14 '21 at 05:18
  • Do you mean other methods works on raspi? It can only be the Qt Bluetooth implementation on Linux / BlueZ, because it works on Android. You can take a look at this implementation in the path Src/qtconnectivity/src/bluetooth/bluez. Might be it will help you to understand why it dosn't work. – AD1170 Apr 14 '21 at 06:49