blob: ab393210ab57bb96c0c8872cc46d0dc6196f9dc0 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtBluetooth module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <private/qtbluetoothglobal_p.h>
#if QT_CONFIG(bluez)
#include <QtBluetooth/private/bluez5_helper_p.h>
#endif
#include <QBluetoothAddress>
#include <QBluetoothLocalDevice>
#include <QBluetoothDeviceDiscoveryAgent>
#include <QBluetoothUuid>
#include <QLowEnergyController>
#include <QLowEnergyCharacteristic>
#include <QDebug>
/*!
This test requires a TI sensor tag with Firmware version: 1.5 (Oct 23 2013).
Since revision updates change user strings and even shift handles around
other versions than the above are unlikely to succeed. Please update the
sensor tag before continuing.
The TI sensor can be updated using the related iOS app. The Android version
doesn't seem to update at this point in time.
*/
QT_USE_NAMESPACE
// This define must be set if the platform provides access to GATT handles
// otherwise it must not be defined. As of now the two supported platforms
// (Android and Bluez/Linux) provide access or some notion of it.
#ifndef Q_OS_MAC
#define HANDLES_PROVIDED_BY_PLATFORM
#endif
#ifdef HANDLES_PROVIDED_BY_PLATFORM
#define HANDLE_COMPARE(actual,expected) \
if (!isBluezDbusLE) {\
QCOMPARE(actual, expected);\
};
#else
#define HANDLE_COMPARE(actual,expected)
#endif
class tst_QLowEnergyController : public QObject
{
Q_OBJECT
public:
tst_QLowEnergyController();
~tst_QLowEnergyController();
private slots:
void initTestCase();
void init();
void cleanupTestCase();
void tst_emptyCtor();
void tst_connect();
void tst_concurrentDiscovery();
void tst_defaultBehavior();
void tst_writeCharacteristic();
void tst_writeCharacteristicNoResponse();
void tst_readWriteDescriptor();
void tst_customProgrammableDevice();
void tst_errorCases();
private:
void verifyServiceProperties(const QLowEnergyService *info);
bool verifyClientCharacteristicValue(const QByteArray& value);
QBluetoothDeviceDiscoveryAgent *devAgent;
QBluetoothAddress remoteDevice;
QBluetoothDeviceInfo remoteDeviceInfo;
QList<QBluetoothUuid> foundServices;
bool isBluezDbusLE = false;
};
tst_QLowEnergyController::tst_QLowEnergyController()
{
//QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
#ifndef Q_OS_MAC
// Core Bluetooth (OS X and iOS) does not work with addresses,
// making the code below useless.
const QString remote = qgetenv("BT_TEST_DEVICE");
if (!remote.isEmpty()) {
remoteDevice = QBluetoothAddress(remote);
qWarning() << "Using remote device " << remote << " for testing. Ensure that the device is discoverable for pairing requests";
} else {
qWarning() << "Not using any remote device for testing. Set BT_TEST_DEVICE env to run manual tests involving a remote device";
}
#endif
#if QT_CONFIG(bluez)
// This debug is needed to determine runtime configuration in the Qt CI.
isBluezDbusLE = (bluetoothdVersion() >= QVersionNumber(5, 42));
qDebug() << "isDBusBluez:" << isBluezDbusLE;
#endif
}
tst_QLowEnergyController::~tst_QLowEnergyController()
{
}
void tst_QLowEnergyController::initTestCase()
{
#if !defined(Q_OS_MAC)
if (remoteDevice.isNull()
#if !QT_CONFIG(winrt_bt)
|| QBluetoothLocalDevice::allDevices().isEmpty()) {
#else
) {
#endif
qWarning("No remote device or local adapter found.");
return;
}
#elif defined(Q_OS_OSX)
// allDevices is always empty on iOS:
if (QBluetoothLocalDevice::allDevices().isEmpty()) {
qWarning("No local adapter found.");
return;
}
#endif
devAgent = new QBluetoothDeviceDiscoveryAgent(this);
devAgent->setLowEnergyDiscoveryTimeout(5000);
QSignalSpy finishedSpy(devAgent, SIGNAL(finished()));
// there should be no changes yet
QVERIFY(finishedSpy.isValid());
QVERIFY(finishedSpy.isEmpty());
bool deviceFound = false;
devAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
QTRY_VERIFY_WITH_TIMEOUT(finishedSpy.count() > 0, 30000);
const QList<QBluetoothDeviceInfo> infos = devAgent->discoveredDevices();
for (const QBluetoothDeviceInfo &info : infos) {
#ifndef Q_OS_MAC
if (info.address() == remoteDevice) {
#else
// On OS X/iOS the only way to find the device we are
// interested in - is to use device's name.
if (info.name().contains("Sensor") && info.name().contains("Tag")) {
#endif
remoteDeviceInfo = info;
deviceFound = true;
break;
}
}
QVERIFY2(deviceFound, "Cannot find remote device.");
// These are the services exported by the TI SensorTag
#ifndef Q_OS_MAC
// Core Bluetooth somehow ignores/hides/fails to discover these services.
if (!isBluezDbusLE) // Bluez LE Dbus intentionally hides 0x1800 service
foundServices << QBluetoothUuid(QString("00001800-0000-1000-8000-00805f9b34fb"));
foundServices << QBluetoothUuid(QString("00001801-0000-1000-8000-00805f9b34fb"));
#endif
foundServices << QBluetoothUuid(QString("0000180a-0000-1000-8000-00805f9b34fb"));
foundServices << QBluetoothUuid(QString("0000ffe0-0000-1000-8000-00805f9b34fb"));
foundServices << QBluetoothUuid(QString("f000aa00-0451-4000-b000-000000000000"));
foundServices << QBluetoothUuid(QString("f000aa10-0451-4000-b000-000000000000"));
foundServices << QBluetoothUuid(QString("f000aa20-0451-4000-b000-000000000000"));
foundServices << QBluetoothUuid(QString("f000aa30-0451-4000-b000-000000000000"));
foundServices << QBluetoothUuid(QString("f000aa40-0451-4000-b000-000000000000"));
foundServices << QBluetoothUuid(QString("f000aa50-0451-4000-b000-000000000000"));
foundServices << QBluetoothUuid(QString("f000aa60-0451-4000-b000-000000000000"));
foundServices << QBluetoothUuid(QString("f000ccc0-0451-4000-b000-000000000000"));
foundServices << QBluetoothUuid(QString("f000ffc0-0451-4000-b000-000000000000"));
}
/*
* Executed in between each test function call.
*/
void tst_QLowEnergyController::init()
{
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_TVOS)
/*
* Add a delay to give Android/iOS stack time to catch up in between
* the multiple connect/disconnects within each test function.
*/
QTest::qWait(2000);
#endif
}
void tst_QLowEnergyController::cleanupTestCase()
{
}
void tst_QLowEnergyController::tst_emptyCtor()
{
{
QBluetoothAddress remoteAddress;
QLowEnergyController control(remoteAddress);
QSignalSpy connectedSpy(&control, SIGNAL(connected()));
QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
QSignalSpy errorSpy(&control, SIGNAL(error(QLowEnergyController::Error)));
QCOMPARE(control.error(), QLowEnergyController::NoError);
control.connectToDevice();
QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 10000);
QVERIFY(connectedSpy.isEmpty());
QVERIFY(stateSpy.isEmpty());
QLowEnergyController::Error lastError = errorSpy[0].at(0).value<QLowEnergyController::Error>();
QVERIFY(lastError == QLowEnergyController::UnknownRemoteDeviceError
|| lastError == QLowEnergyController::InvalidBluetoothAdapterError);
}
{
QBluetoothDeviceInfo deviceInfo;
QLowEnergyController control(deviceInfo);
QSignalSpy connectedSpy(&control, SIGNAL(connected()));
QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
QSignalSpy errorSpy(&control, SIGNAL(error(QLowEnergyController::Error)));
QCOMPARE(control.error(), QLowEnergyController::NoError);
control.connectToDevice();
QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 10000);
QVERIFY(connectedSpy.isEmpty());
QVERIFY(stateSpy.isEmpty());
QLowEnergyController::Error lastError = errorSpy[0].at(0).value<QLowEnergyController::Error>();
QVERIFY(lastError == QLowEnergyController::UnknownRemoteDeviceError // if local device on platform found
|| lastError == QLowEnergyController::InvalidBluetoothAdapterError); // otherwise, e.g. fallback backend
}
}
void tst_QLowEnergyController::tst_connect()
{
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) || QT_CONFIG(winrt_bt)
if (!remoteDeviceInfo.isValid())
#else
if (localAdapters.isEmpty() || !remoteDeviceInfo.isValid())
#endif
QSKIP("No local Bluetooth or remote BTLE device found. Skipping test.");
QLowEnergyController control(remoteDeviceInfo);
QCOMPARE(remoteDeviceInfo.deviceUuid(), control.remoteDeviceUuid());
QCOMPARE(control.role(), QLowEnergyController::CentralRole);
QSignalSpy connectedSpy(&control, SIGNAL(connected()));
QSignalSpy disconnectedSpy(&control, SIGNAL(disconnected()));
if (remoteDeviceInfo.name().isEmpty())
QVERIFY(control.remoteName().isEmpty());
else
QCOMPARE(control.remoteName(), remoteDeviceInfo.name());
#if !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !QT_CONFIG(winrt_bt)
const QBluetoothAddress localAdapter = localAdapters.at(0).address();
QCOMPARE(control.localAddress(), localAdapter);
QVERIFY(!control.localAddress().isNull());
#endif
#ifndef Q_OS_MAC
QCOMPARE(control.remoteAddress(), remoteDevice);
#endif
QCOMPARE(control.state(), QLowEnergyController::UnconnectedState);
QCOMPARE(control.error(), QLowEnergyController::NoError);
QVERIFY(control.errorString().isEmpty());
QCOMPARE(disconnectedSpy.count(), 0);
QCOMPARE(connectedSpy.count(), 0);
QVERIFY(control.services().isEmpty());
bool wasError = false;
control.connectToDevice();
QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
10000);
QCOMPARE(disconnectedSpy.count(), 0);
if (control.error() != QLowEnergyController::NoError) {
//error during connect
QCOMPARE(connectedSpy.count(), 0);
QCOMPARE(control.state(), QLowEnergyController::UnconnectedState);
wasError = true;
} else if (control.state() == QLowEnergyController::ConnectingState) {
//timeout
QCOMPARE(connectedSpy.count(), 0);
QVERIFY(control.errorString().isEmpty());
QCOMPARE(control.error(), QLowEnergyController::NoError);
QVERIFY(control.services().isEmpty());
QSKIP("Connection to LE device cannot be established. Skipping test.");
return;
} else {
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
QCOMPARE(connectedSpy.count(), 1);
QCOMPARE(control.error(), QLowEnergyController::NoError);
QVERIFY(control.errorString().isEmpty());
}
QVERIFY(control.services().isEmpty());
QList<QLowEnergyService *> savedReferences;
if (!wasError) {
QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
QSignalSpy serviceFoundSpy(&control, SIGNAL(serviceDiscovered(QBluetoothUuid)));
QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
control.discoverServices();
QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
QCOMPARE(stateSpy.count(), 2);
QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveringState);
QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveredState);
QVERIFY(!serviceFoundSpy.isEmpty());
QVERIFY(serviceFoundSpy.count() >= foundServices.count());
QVERIFY(!serviceFoundSpy.isEmpty());
QList<QBluetoothUuid> listing;
for (int i = 0; i < serviceFoundSpy.count(); i++) {
const QVariant v = serviceFoundSpy[i].at(0);
listing.append(v.value<QBluetoothUuid>());
}
for (const QBluetoothUuid &uuid : qAsConst(foundServices)) {
QVERIFY2(listing.contains(uuid),
uuid.toString().toLatin1());
QLowEnergyService *service = control.createServiceObject(uuid);
QVERIFY2(service, uuid.toString().toLatin1());
savedReferences.append(service);
QCOMPARE(service->type(), QLowEnergyService::PrimaryService);
QCOMPARE(service->state(), QLowEnergyService::DiscoveryRequired);
}
// unrelated uuids don't return valid service object
// invalid service uuid
QVERIFY(!control.createServiceObject(QBluetoothUuid()));
// some random uuid
QVERIFY(!control.createServiceObject(QBluetoothUuid(QBluetoothUuid::DeviceName)));
// initiate characteristic discovery
for (QLowEnergyService *service : qAsConst(savedReferences)) {
qDebug() << "Discovering" << service->serviceUuid();
QSignalSpy stateSpy(service,
SIGNAL(stateChanged(QLowEnergyService::ServiceState)));
QSignalSpy errorSpy(service, SIGNAL(error(QLowEnergyService::ServiceError)));
service->discoverDetails();
QTRY_VERIFY_WITH_TIMEOUT(
service->state() == QLowEnergyService::ServiceDiscovered, 10000);
QCOMPARE(errorSpy.count(), 0); //no error
QCOMPARE(stateSpy.count(), 2); //
verifyServiceProperties(service);
}
// ensure that related service objects share same state
for (QLowEnergyService* originalService : qAsConst(savedReferences)) {
QLowEnergyService *newService = control.createServiceObject(
originalService->serviceUuid());
QVERIFY(newService);
QCOMPARE(newService->state(), QLowEnergyService::ServiceDiscovered);
delete newService;
}
}
// Finish off
control.disconnectFromDevice();
QTRY_VERIFY_WITH_TIMEOUT(
control.state() == QLowEnergyController::UnconnectedState,
10000);
if (wasError) {
QCOMPARE(disconnectedSpy.count(), 0);
} else {
QCOMPARE(disconnectedSpy.count(), 1);
// after disconnect all service references must be invalid
for (const QLowEnergyService *entry : qAsConst(savedReferences)) {
const QBluetoothUuid &uuid = entry->serviceUuid();
QVERIFY2(entry->state() == QLowEnergyService::InvalidService,
uuid.toString().toLatin1());
//after disconnect all related characteristics and descriptors are invalid
QList<QLowEnergyCharacteristic> chars = entry->characteristics();
for (int i = 0; i < chars.count(); i++) {
QCOMPARE(chars.at(i).isValid(), false);
QList<QLowEnergyDescriptor> descriptors = chars[i].descriptors();
for (int j = 0; j < descriptors.count(); j++)
QCOMPARE(descriptors[j].isValid(), false);
}
}
}
qDeleteAll(savedReferences);
savedReferences.clear();
}
void tst_QLowEnergyController::tst_concurrentDiscovery()
{
#if !defined(Q_OS_MACOS) && !QT_CONFIG(winrt_bt)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");
#endif
if (!remoteDeviceInfo.isValid())
QSKIP("No remote BTLE device found. Skipping test.");
QLowEnergyController control(remoteDeviceInfo);
QCOMPARE(control.state(), QLowEnergyController::UnconnectedState);
QCOMPARE(control.error(), QLowEnergyController::NoError);
control.connectToDevice();
{
QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
30000);
}
if (control.state() == QLowEnergyController::ConnectingState
|| control.error() != QLowEnergyController::NoError) {
// default BTLE backend forever hangs in ConnectingState
QSKIP("Cannot connect to remote device");
}
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
// 2. new controller to same device fails
{
#ifdef Q_OS_DARWIN
QLowEnergyController control2(remoteDeviceInfo);
#else
QLowEnergyController control2(remoteDevice);
#endif
control2.connectToDevice();
{
QTRY_IMPL(control2.state() != QLowEnergyController::ConnectingState,
30000);
}
#if defined(Q_OS_ANDROID) || defined(Q_OS_DARWIN) || QT_CONFIG(winrt_bt)
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
QCOMPARE(control2.state(), QLowEnergyController::ConnectedState);
control2.disconnectFromDevice();
QTest::qWait(3000);
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
QCOMPARE(control2.state(), QLowEnergyController::UnconnectedState);
#else
if (!isBluezDbusLE) {
// see QTBUG-42519
// Linux non-DBus GATT cannot maintain two controller connections at the same time
QCOMPARE(control.state(), QLowEnergyController::UnconnectedState);
QCOMPARE(control2.state(), QLowEnergyController::ConnectedState);
control2.disconnectFromDevice();
QTRY_COMPARE(control2.state(), QLowEnergyController::UnconnectedState);
QTRY_COMPARE(control2.error(), QLowEnergyController::NoError);
// reconnect control
control.connectToDevice();
{
QTRY_VERIFY_WITH_TIMEOUT(control.state() != QLowEnergyController::ConnectingState,
30000);
}
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
} else {
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
QCOMPARE(control2.state(), QLowEnergyController::ConnectedState);
control2.disconnectFromDevice();
QTRY_COMPARE(control2.state(), QLowEnergyController::UnconnectedState);
QTRY_COMPARE(control2.error(), QLowEnergyController::NoError);
QTRY_COMPARE(control.state(), QLowEnergyController::UnconnectedState);
// reconnect control
control.connectToDevice();
{
QTRY_VERIFY_WITH_TIMEOUT(control.state() != QLowEnergyController::ConnectingState,
30000);
}
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
}
#endif
}
/* We are testing that we can run service discovery on the same device
* for multiple services at the same time.
* */
QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
control.discoverServices();
QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
QCOMPARE(stateSpy.count(), 2);
QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveringState);
QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveredState);
// pick MAX_SERVICES_SAME_TIME_ACCESS services
// and discover them at the same time
#define MAX_SERVICES_SAME_TIME_ACCESS 3
QLowEnergyService *services[MAX_SERVICES_SAME_TIME_ACCESS];
QVERIFY(control.services().count() >= MAX_SERVICES_SAME_TIME_ACCESS);
QList<QBluetoothUuid> uuids = control.services();
// initialize services
for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) {
services[i] = control.createServiceObject(uuids.at(i), this);
QVERIFY(services[i]);
}
// start complete discovery
for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++)
services[i]->discoverDetails();
// wait until discovery done
for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) {
qWarning() << "Waiting for" << i << services[i]->serviceUuid();
QTRY_VERIFY_WITH_TIMEOUT(
services[i]->state() == QLowEnergyService::ServiceDiscovered,
30000);
}
// verify discovered services
for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) {
verifyServiceProperties(services[i]);
QVERIFY(!services[i]->contains(QLowEnergyCharacteristic()));
QVERIFY(!services[i]->contains(QLowEnergyDescriptor()));
}
control.disconnectFromDevice();
QTRY_VERIFY_WITH_TIMEOUT(control.state() == QLowEnergyController::UnconnectedState,
30000);
discoveryFinishedSpy.clear();
// redo the discovery with same controller
QLowEnergyService *services_second[MAX_SERVICES_SAME_TIME_ACCESS];
control.connectToDevice();
{
QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
30000);
}
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
stateSpy.clear();
control.discoverServices();
QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
QCOMPARE(stateSpy.count(), 2);
QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveringState);
QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveredState);
// get all details
for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) {
services_second[i] = control.createServiceObject(uuids.at(i), this);
QVERIFY(services_second[i]->parent() == this);
QVERIFY(services[i]);
QVERIFY(services_second[i]->state() == QLowEnergyService::DiscoveryRequired);
services_second[i]->discoverDetails();
}
// wait until discovery done
for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) {
qWarning() << "Waiting for" << i << services_second[i]->serviceUuid();
QTRY_VERIFY_WITH_TIMEOUT(
services_second[i]->state() == QLowEnergyService::ServiceDiscovered,
30000);
QCOMPARE(services_second[i]->serviceName(), services[i]->serviceName());
QCOMPARE(services_second[i]->serviceUuid(), services[i]->serviceUuid());
}
// verify discovered services (1st and 2nd round)
for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) {
verifyServiceProperties(services_second[i]);
//after disconnect all related characteristics and descriptors are invalid
const QList<QLowEnergyCharacteristic> chars = services[i]->characteristics();
for (int j = 0; j < chars.count(); j++) {
QCOMPARE(chars.at(j).isValid(), false);
QVERIFY(services[i]->contains(chars[j]));
QVERIFY(!services_second[i]->contains(chars[j]));
const QList<QLowEnergyDescriptor> descriptors = chars[j].descriptors();
for (int k = 0; k < descriptors.count(); k++) {
QCOMPARE(descriptors[k].isValid(), false);
services[i]->contains(descriptors[k]);
QVERIFY(!services_second[i]->contains(chars[j]));
}
}
QCOMPARE(services[i]->serviceUuid(), services_second[i]->serviceUuid());
QCOMPARE(services[i]->serviceName(), services_second[i]->serviceName());
QCOMPARE(services[i]->type(), services_second[i]->type());
QVERIFY(services[i]->state() == QLowEnergyService::InvalidService);
QVERIFY(services_second[i]->state() == QLowEnergyService::ServiceDiscovered);
}
// cleanup
for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) {
delete services[i];
delete services_second[i];
}
control.disconnectFromDevice();
}
void tst_QLowEnergyController::verifyServiceProperties(
const QLowEnergyService *info)
{
if (info->serviceUuid() ==
QBluetoothUuid(QString("00001800-0000-1000-8000-00805f9b34fb"))) {
qDebug() << "Verifying GAP Service";
QList<QLowEnergyCharacteristic> chars = info->characteristics();
QCOMPARE(chars.count(), 5);
// Device Name
QString temp("00002a00-0000-1000-8000-00805f9b34fb");
QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x3));
QCOMPARE(chars[0].properties(), QLowEnergyCharacteristic::Read);
QCOMPARE(chars[0].value(), QByteArray::fromHex("544920424c452053656e736f7220546167"));
QVERIFY(chars[0].isValid());
QCOMPARE(chars[0].descriptors().count(), 0);
QVERIFY(info->contains(chars[0]));
// Appearance
temp = QString("00002a01-0000-1000-8000-00805f9b34fb");
QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x5));
QCOMPARE(chars[1].properties(), QLowEnergyCharacteristic::Read);
QCOMPARE(chars[1].value(), QByteArray::fromHex("0000"));
QVERIFY(chars[1].isValid());
QCOMPARE(chars[1].descriptors().count(), 0);
QVERIFY(info->contains(chars[1]));
// Peripheral Privacy Flag
temp = QString("00002a02-0000-1000-8000-00805f9b34fb");
QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x7));
QVERIFY(chars[2].properties() & QLowEnergyCharacteristic::Read);
QCOMPARE(chars[2].value(), QByteArray::fromHex("00"));
QVERIFY(chars[2].isValid());
QCOMPARE(chars[2].descriptors().count(), 0);
QVERIFY(info->contains(chars[2]));
// Reconnection Address
temp = QString("00002a03-0000-1000-8000-00805f9b34fb");
QCOMPARE(chars[3].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[3].handle(), QLowEnergyHandle(0x9));
//Early firmware version had this characteristic as Read|Write and may fail
QCOMPARE(chars[3].properties(), QLowEnergyCharacteristic::Write);
if (chars[3].properties() & QLowEnergyCharacteristic::Read)
QCOMPARE(chars[3].value(), QByteArray::fromHex("000000000000"));
else
QCOMPARE(chars[3].value(), QByteArray());
QVERIFY(chars[3].isValid());
QCOMPARE(chars[3].descriptors().count(), 0);
QVERIFY(info->contains(chars[3]));
// Peripheral Preferred Connection Parameters
temp = QString("00002a04-0000-1000-8000-00805f9b34fb");
QCOMPARE(chars[4].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[4].handle(), QLowEnergyHandle(0xb));
QCOMPARE(chars[4].properties(), QLowEnergyCharacteristic::Read);
QCOMPARE(chars[4].value(), QByteArray::fromHex("5000a0000000e803"));
QVERIFY(chars[4].isValid());
QCOMPARE(chars[4].descriptors().count(), 0);
QVERIFY(info->contains(chars[4]));
} else if (info->serviceUuid() ==
QBluetoothUuid(QString("00001801-0000-1000-8000-00805f9b34fb"))) {
qDebug() << "Verifying GATT Service";
QList<QLowEnergyCharacteristic> chars = info->characteristics();
QCOMPARE(chars.count(), 1);
// Service Changed
QString temp("00002a05-0000-1000-8000-00805f9b34fb");
//this should really be readable according to GATT Service spec
QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0xe));
QCOMPARE(chars[0].properties(), QLowEnergyCharacteristic::Indicate);
QCOMPARE(chars[0].value(), QByteArray());
QVERIFY(chars[0].isValid());
QVERIFY(info->contains(chars[0]));
QCOMPARE(chars[0].descriptors().count(), 1);
QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0xf));
QCOMPARE(chars[0].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
QCOMPARE(chars[0].descriptors().at(0).type(),
QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
QVERIFY(info->contains(chars[0].descriptors().at(0)));
} else if (info->serviceUuid() ==
QBluetoothUuid(QString("0000180a-0000-1000-8000-00805f9b34fb"))) {
qDebug() << "Verifying Device Information";
QList<QLowEnergyCharacteristic> chars = info->characteristics();
QCOMPARE(chars.count(), 9);
// System ID
QString temp("00002a23-0000-1000-8000-00805f9b34fb");
//this should really be readable according to GATT Service spec
QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x12));
QCOMPARE(chars[0].properties(), QLowEnergyCharacteristic::Read);
// Do not read the System ID as it is different for every device
// QEXPECT_FAIL("", "The value is different on different devices", Continue);
// QCOMPARE(chars[0].value(), QByteArray::fromHex("6e41ab0000296abc"));
QVERIFY(chars[0].isValid());
QVERIFY(info->contains(chars[0]));
QCOMPARE(chars[0].descriptors().count(), 0);
// Model Number
temp = QString("00002a24-0000-1000-8000-00805f9b34fb");
QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x14));
QCOMPARE(chars[1].properties(), QLowEnergyCharacteristic::Read);
QCOMPARE(chars[1].value(), QByteArray::fromHex("4e2e412e00"));
QVERIFY(chars[1].isValid());
QVERIFY(info->contains(chars[1]));
QCOMPARE(chars[1].descriptors().count(), 0);
// Serial Number
temp = QString("00002a25-0000-1000-8000-00805f9b34fb");
QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x16));
QCOMPARE(chars[2].properties(),
(QLowEnergyCharacteristic::Read));
QCOMPARE(chars[2].value(), QByteArray::fromHex("4e2e412e00"));
QVERIFY(chars[2].isValid());
QVERIFY(info->contains(chars[2]));
QCOMPARE(chars[2].descriptors().count(), 0);
// Firmware Revision
temp = QString("00002a26-0000-1000-8000-00805f9b34fb");
QCOMPARE(chars[3].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[3].handle(), QLowEnergyHandle(0x18));
QCOMPARE(chars[3].properties(),
(QLowEnergyCharacteristic::Read));
//FW rev. : 1.5 (Oct 23 2013)
// Other revisions will fail here
QCOMPARE(chars[3].value(), QByteArray::fromHex("312e3520284f637420323320323031332900"));
QVERIFY(chars[3].isValid());
QVERIFY(info->contains(chars[3]));
QCOMPARE(chars[3].descriptors().count(), 0);
// Hardware Revision
temp = QString("00002a27-0000-1000-8000-00805f9b34fb");
QCOMPARE(chars[4].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[4].handle(), QLowEnergyHandle(0x1a));
QCOMPARE(chars[4].properties(),
(QLowEnergyCharacteristic::Read));
QCOMPARE(chars[4].value(), QByteArray::fromHex("4e2e412e00"));
QVERIFY(chars[4].isValid());
QVERIFY(info->contains(chars[4]));
QCOMPARE(chars[4].descriptors().count(), 0);
// Software Revision
temp = QString("00002a28-0000-1000-8000-00805f9b34fb");
QCOMPARE(chars[5].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[5].handle(), QLowEnergyHandle(0x1c));
QCOMPARE(chars[5].properties(),
(QLowEnergyCharacteristic::Read));
QCOMPARE(chars[5].value(), QByteArray::fromHex("4e2e412e00"));
QVERIFY(chars[5].isValid());
QVERIFY(info->contains(chars[5]));
QCOMPARE(chars[5].descriptors().count(), 0);
// Manufacturer Name
temp = QString("00002a29-0000-1000-8000-00805f9b34fb");
QCOMPARE(chars[6].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[6].handle(), QLowEnergyHandle(0x1e));
QCOMPARE(chars[6].properties(),
(QLowEnergyCharacteristic::Read));
QCOMPARE(chars[6].value(), QByteArray::fromHex("546578617320496e737472756d656e747300"));
QVERIFY(chars[6].isValid());
QVERIFY(info->contains(chars[6]));
QCOMPARE(chars[6].descriptors().count(), 0);
// IEEE
temp = QString("00002a2a-0000-1000-8000-00805f9b34fb");
QCOMPARE(chars[7].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[7].handle(), QLowEnergyHandle(0x20));
QCOMPARE(chars[7].properties(),
(QLowEnergyCharacteristic::Read));
QCOMPARE(chars[7].value(), QByteArray::fromHex("fe006578706572696d656e74616c"));
QVERIFY(chars[7].isValid());
QVERIFY(info->contains(chars[7]));
QCOMPARE(chars[7].descriptors().count(), 0);
// PnP ID
temp = QString("00002a50-0000-1000-8000-00805f9b34fb");
QCOMPARE(chars[8].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[8].handle(), QLowEnergyHandle(0x22));
QCOMPARE(chars[8].properties(),
(QLowEnergyCharacteristic::Read));
QCOMPARE(chars[8].value(), QByteArray::fromHex("010d0000001001"));
QVERIFY(chars[8].isValid());
QVERIFY(info->contains(chars[8]));
QCOMPARE(chars[8].descriptors().count(), 0);
} else if (info->serviceUuid() ==
QBluetoothUuid(QString("f000aa00-0451-4000-b000-000000000000"))) {
qDebug() << "Verifying Temperature";
QList<QLowEnergyCharacteristic> chars = info->characteristics();
QVERIFY(chars.count() >= 2);
// Temp Data
QString temp("f000aa01-0451-4000-b000-000000000000");
QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x25));
QCOMPARE(chars[0].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify));
QCOMPARE(chars[0].value(), QByteArray::fromHex("00000000"));
QVERIFY(chars[0].isValid());
QVERIFY(info->contains(chars[0]));
QCOMPARE(chars[0].descriptors().count(), 2);
//descriptor checks
QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x26));
QCOMPARE(chars[0].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
QCOMPARE(chars[0].descriptors().at(0).type(),
QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
QVERIFY(info->contains(chars[0].descriptors().at(0)));
QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x27));
QCOMPARE(chars[0].descriptors().at(1).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[0].descriptors().at(1).type(),
QBluetoothUuid::CharacteristicUserDescription);
// value different in other revisions and test may fail
QCOMPARE(chars[0].descriptors().at(1).value(),
QByteArray::fromHex("54656d702e2044617461"));
QVERIFY(info->contains(chars[0].descriptors().at(1)));
// Temp Config
temp = QString("f000aa02-0451-4000-b000-000000000000");
QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x29));
QCOMPARE(chars[1].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
QCOMPARE(chars[1].value(), QByteArray::fromHex("00"));
QVERIFY(chars[1].isValid());
QVERIFY(info->contains(chars[1]));
QCOMPARE(chars[1].descriptors().count(), 1);
//descriptor checks
QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x2a));
QCOMPARE(chars[1].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[1].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
// value different in other revisions and test may fail
QCOMPARE(chars[1].descriptors().at(0).value(),
QByteArray::fromHex("54656d702e20436f6e662e"));
QVERIFY(info->contains(chars[1].descriptors().at(0)));
//Temp Period (introduced by later firmware versions)
if (chars.count() > 2) {
temp = QString("f000aa03-0451-4000-b000-000000000000");
QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x2c));
QCOMPARE(chars[2].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
QCOMPARE(chars[2].value(), QByteArray::fromHex("64"));
QVERIFY(chars[2].isValid());
QVERIFY(info->contains(chars[2]));
QCOMPARE(chars[2].descriptors().count(), 1);
//descriptor checks
QCOMPARE(chars[2].descriptors().at(0).isValid(), true);
HANDLE_COMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x2d));
QCOMPARE(chars[2].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[2].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[2].descriptors().at(0).value(),
QByteArray::fromHex("54656d702e20506572696f64"));
QVERIFY(info->contains(chars[2].descriptors().at(0)));
}
} else if (info->serviceUuid() ==
QBluetoothUuid(QString("0000ffe0-0000-1000-8000-00805f9b34fb"))) {
qDebug() << "Verifying Simple Keys";
QList<QLowEnergyCharacteristic> chars = info->characteristics();
QCOMPARE(chars.count(), 1);
// Temp Data
QString temp("0000ffe1-0000-1000-8000-00805f9b34fb");
QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x6b));
QCOMPARE(chars[0].properties(),
(QLowEnergyCharacteristic::Notify));
QCOMPARE(chars[0].value(), QByteArray());
QVERIFY(chars[0].isValid());
QVERIFY(info->contains(chars[0]));
QCOMPARE(chars[0].descriptors().count(), 2);
//descriptor checks
QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x6c));
QCOMPARE(chars[0].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
QCOMPARE(chars[0].descriptors().at(0).type(),
QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
QVERIFY(info->contains(chars[0].descriptors().at(0)));
QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x6d));
QCOMPARE(chars[0].descriptors().at(1).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[0].descriptors().at(1).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[0].descriptors().at(1).value(),
QByteArray::fromHex("4b6579205072657373205374617465"));
QVERIFY(info->contains(chars[0].descriptors().at(1)));
} else if (info->serviceUuid() ==
QBluetoothUuid(QString("f000aa10-0451-4000-b000-000000000000"))) {
qDebug() << "Verifying Accelerometer";
QList<QLowEnergyCharacteristic> chars = info->characteristics();
QCOMPARE(chars.count(), 3);
// Accel Data
QString temp("f000aa11-0451-4000-b000-000000000000");
QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x30));
QCOMPARE(chars[0].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify));
QCOMPARE(chars[0].value(), QByteArray::fromHex("000000"));
QVERIFY(chars[0].isValid());
QVERIFY(info->contains(chars[0]));
QCOMPARE(chars[0].descriptors().count(), 2);
QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x31));
QCOMPARE(chars[0].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
QCOMPARE(chars[0].descriptors().at(0).type(),
QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
QVERIFY(info->contains(chars[0].descriptors().at(0)));
QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x32));
QCOMPARE(chars[0].descriptors().at(1).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[0].descriptors().at(1).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[0].descriptors().at(1).value(),
QByteArray::fromHex("416363656c2e2044617461"));
QVERIFY(info->contains(chars[0].descriptors().at(1)));
// Accel Config
temp = QString("f000aa12-0451-4000-b000-000000000000");
QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x34));
QCOMPARE(chars[1].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
QCOMPARE(chars[1].value(), QByteArray::fromHex("00"));
QVERIFY(chars[1].isValid());
QVERIFY(info->contains(chars[1]));
QCOMPARE(chars[1].descriptors().count(), 1);
QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x35));
QCOMPARE(chars[1].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[1].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[1].descriptors().at(0).value(),
QByteArray::fromHex("416363656c2e20436f6e662e"));
QVERIFY(info->contains(chars[1].descriptors().at(0)));
// Accel Period
temp = QString("f000aa13-0451-4000-b000-000000000000");
QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x37));
QCOMPARE(chars[2].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
QCOMPARE(chars[2].value(), QByteArray::fromHex("64")); // don't change it or set it to 0x64
QVERIFY(chars[2].isValid());
QVERIFY(info->contains(chars[2]));
QCOMPARE(chars[2].descriptors().count(), 1);
//descriptor checks
QCOMPARE(chars[2].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x38));
QCOMPARE(chars[2].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[2].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
// value different in other revisions and test may fail
QCOMPARE(chars[2].descriptors().at(0).value(),
QByteArray::fromHex("416363656c2e20506572696f64"));
QVERIFY(info->contains(chars[2].descriptors().at(0)));
} else if (info->serviceUuid() ==
QBluetoothUuid(QString("f000aa20-0451-4000-b000-000000000000"))) {
qDebug() << "Verifying Humidity";
QList<QLowEnergyCharacteristic> chars = info->characteristics();
QVERIFY(chars.count() >= 2); //new firmware has more chars
// Humidity Data
QString temp("f000aa21-0451-4000-b000-000000000000");
QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x3b));
QCOMPARE(chars[0].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify));
QCOMPARE(chars[0].value(), QByteArray::fromHex("00000000"));
QVERIFY(chars[0].isValid());
QVERIFY(info->contains(chars[0]));
QCOMPARE(chars[0].descriptors().count(), 2);
//descriptor checks
QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x3c));
QCOMPARE(chars[0].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
QCOMPARE(chars[0].descriptors().at(0).type(),
QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
QVERIFY(info->contains(chars[0].descriptors().at(0)));
QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x3d));
QCOMPARE(chars[0].descriptors().at(1).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[0].descriptors().at(1).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[0].descriptors().at(1).value(),
QByteArray::fromHex("48756d69642e2044617461"));
QVERIFY(info->contains(chars[0].descriptors().at(1)));
// Humidity Config
temp = QString("f000aa22-0451-4000-b000-000000000000");
QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x3f));
QCOMPARE(chars[1].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
QCOMPARE(chars[1].value(), QByteArray::fromHex("00"));
QVERIFY(chars[1].isValid());
QVERIFY(info->contains(chars[1]));
QCOMPARE(chars[1].descriptors().count(), 1);
//descriptor checks
QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x40));
QCOMPARE(chars[1].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[1].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[1].descriptors().at(0).value(),
QByteArray::fromHex("48756d69642e20436f6e662e"));
QVERIFY(info->contains(chars[1].descriptors().at(0)));
if (chars.count() >= 3) {
// New firmware new characteristic
// Humidity Period
temp = QString("f000aa23-0451-4000-b000-000000000000");
QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x42));
QCOMPARE(chars[2].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
QCOMPARE(chars[2].value(), QByteArray::fromHex("64"));
QVERIFY(chars[2].isValid());
QVERIFY(info->contains(chars[2]));
QCOMPARE(chars[2].descriptors().count(), 1);
//descriptor checks
QCOMPARE(chars[2].descriptors().at(0).isValid(), true);
HANDLE_COMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x43));
QCOMPARE(chars[2].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[2].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[2].descriptors().at(0).value(),
QByteArray::fromHex("48756d69642e20506572696f64"));
QVERIFY(info->contains(chars[2].descriptors().at(0)));
}
} else if (info->serviceUuid() ==
QBluetoothUuid(QString("f000aa30-0451-4000-b000-000000000000"))) {
qDebug() << "Verifying Magnetometer";
QList<QLowEnergyCharacteristic> chars = info->characteristics();
QCOMPARE(chars.count(), 3);
// Magnetometer Data
QString temp("f000aa31-0451-4000-b000-000000000000");
QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x46));
QCOMPARE(chars[0].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify));
QCOMPARE(chars[0].value(), QByteArray::fromHex("000000000000"));
QVERIFY(chars[0].isValid());
QVERIFY(info->contains(chars[0]));
QCOMPARE(chars[0].descriptors().count(), 2);
QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x47));
QCOMPARE(chars[0].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
QCOMPARE(chars[0].descriptors().at(0).type(),
QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
QVERIFY(info->contains(chars[0].descriptors().at(0)));
QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x48));
QCOMPARE(chars[0].descriptors().at(1).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[0].descriptors().at(1).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[0].descriptors().at(1).value(),
QByteArray::fromHex("4d61676e2e2044617461"));
QVERIFY(info->contains(chars[0].descriptors().at(1)));
// Magnetometer Config
temp = QString("f000aa32-0451-4000-b000-000000000000");
QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x4a));
QCOMPARE(chars[1].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
QCOMPARE(chars[1].value(), QByteArray::fromHex("00"));
QVERIFY(chars[1].isValid());
QVERIFY(info->contains(chars[1]));
QCOMPARE(chars[1].descriptors().count(), 1);
QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x4b));
QCOMPARE(chars[1].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[1].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
// value different in other revisions and test may fail
QCOMPARE(chars[1].descriptors().at(0).value(),
QByteArray::fromHex("4d61676e2e20436f6e662e"));
QVERIFY(info->contains(chars[1].descriptors().at(0)));
// Magnetometer Period
temp = QString("f000aa33-0451-4000-b000-000000000000");
QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x4d));
QCOMPARE(chars[2].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
QCOMPARE(chars[2].value(), QByteArray::fromHex("c8")); // don't change it or set it to 0xc8
QVERIFY(chars[2].isValid());
QVERIFY(info->contains(chars[2]));
QCOMPARE(chars[2].descriptors().count(), 1);
QCOMPARE(chars[2].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x4e));
QCOMPARE(chars[2].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[2].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
// value different in other revisions and test may fail
QCOMPARE(chars[2].descriptors().at(0).value(),
QByteArray::fromHex("4d61676e2e20506572696f64"));
QVERIFY(info->contains(chars[2].descriptors().at(0)));
} else if (info->serviceUuid() ==
QBluetoothUuid(QString("f000aa40-0451-4000-b000-000000000000"))) {
qDebug() << "Verifying Pressure";
const QList<QLowEnergyCharacteristic> chars = info->characteristics();
QVERIFY(chars.count() >= 3);
// Pressure Data
QString temp("f000aa41-0451-4000-b000-000000000000");
QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x51));
QCOMPARE(chars[0].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify));
QCOMPARE(chars[0].value(), QByteArray::fromHex("00000000"));
QVERIFY(chars[0].isValid());
QVERIFY(info->contains(chars[0]));
QCOMPARE(chars[0].descriptors().count(), 2);
//descriptor checks
QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x52));
QCOMPARE(chars[0].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
QCOMPARE(chars[0].descriptors().at(0).type(),
QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
QVERIFY(info->contains(chars[0].descriptors().at(0)));
QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x53));
QCOMPARE(chars[0].descriptors().at(1).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[0].descriptors().at(1).type(),
QBluetoothUuid::CharacteristicUserDescription);
// value different in other revisions and test may fail
QCOMPARE(chars[0].descriptors().at(1).value(),
QByteArray::fromHex("4261726f6d2e2044617461"));
QVERIFY(info->contains(chars[0].descriptors().at(1)));
// Pressure Config
temp = QString("f000aa42-0451-4000-b000-000000000000");
QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x55));
QCOMPARE(chars[1].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
QCOMPARE(chars[1].value(), QByteArray::fromHex("00"));
QVERIFY(chars[1].isValid());
QVERIFY(info->contains(chars[1]));
QCOMPARE(chars[1].descriptors().count(), 1);
QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x56));
QCOMPARE(chars[1].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[1].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[1].descriptors().at(0).value(),
QByteArray::fromHex("4261726f6d2e20436f6e662e"));
QVERIFY(info->contains(chars[1].descriptors().at(0)));
//calibration and period characteristic are swapped, ensure we don't depend on their order
QLowEnergyCharacteristic calibration, period;
for (const QLowEnergyCharacteristic &ch : chars) {
//find calibration characteristic
if (ch.uuid() == QBluetoothUuid(QString("f000aa43-0451-4000-b000-000000000000")))
calibration = ch;
else if (ch.uuid() == QBluetoothUuid(QString("f000aa44-0451-4000-b000-000000000000")))
period = ch;
}
if (calibration.isValid()) {
// Pressure Calibration
temp = QString("f000aa43-0451-4000-b000-000000000000");
QCOMPARE(calibration.uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(calibration.handle(), QLowEnergyHandle(0x5b));
QCOMPARE(calibration.properties(),
(QLowEnergyCharacteristic::Read));
QCOMPARE(calibration.value(), QByteArray::fromHex("00000000000000000000000000000000")); // don't change it
QVERIFY(calibration.isValid());
QVERIFY(info->contains(calibration));
QCOMPARE(calibration.descriptors().count(), 2);
//descriptor checks
QCOMPARE(calibration.descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(calibration.descriptors().at(0).handle(), QLowEnergyHandle(0x5c));
QCOMPARE(calibration.descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
QCOMPARE(calibration.descriptors().at(0).type(),
QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(verifyClientCharacteristicValue(calibration.descriptors().at(0).value()));
QVERIFY(info->contains(calibration.descriptors().at(0)));
QCOMPARE(calibration.descriptors().at(1).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(calibration.descriptors().at(1).handle(), QLowEnergyHandle(0x5d));
QCOMPARE(calibration.descriptors().at(1).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(calibration.descriptors().at(1).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(calibration.descriptors().at(1).value(),
QByteArray::fromHex("4261726f6d2e2043616c6962722e"));
QVERIFY(info->contains(calibration.descriptors().at(1)));
}
if (period.isValid()) {
// Period Calibration
temp = QString("f000aa44-0451-4000-b000-000000000000");
QCOMPARE(period.uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(period.handle(), QLowEnergyHandle(0x58));
QCOMPARE(period.properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
QCOMPARE(period.value(), QByteArray::fromHex("64"));
QVERIFY(period.isValid());
QVERIFY(info->contains(period));
QCOMPARE(period.descriptors().count(), 1);
//descriptor checks
QCOMPARE(period.descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(period.descriptors().at(0).handle(), QLowEnergyHandle(0x59));
QCOMPARE(period.descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(period.descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(period.descriptors().at(0).value(),
QByteArray::fromHex("4261726f6d2e20506572696f64"));
QVERIFY(info->contains(period.descriptors().at(0)));
}
} else if (info->serviceUuid() ==
QBluetoothUuid(QString("f000aa50-0451-4000-b000-000000000000"))) {
qDebug() << "Verifying Gyroscope";
QList<QLowEnergyCharacteristic> chars = info->characteristics();
QVERIFY(chars.count() >= 2);
// Gyroscope Data
QString temp("f000aa51-0451-4000-b000-000000000000");
QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x60));
QCOMPARE(chars[0].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify));
QCOMPARE(chars[0].value(), QByteArray::fromHex("000000000000"));
QVERIFY(chars[0].isValid());
QVERIFY(info->contains(chars[0]));
QCOMPARE(chars[0].descriptors().count(), 2);
//descriptor checks
QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x61));
QCOMPARE(chars[0].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
QCOMPARE(chars[0].descriptors().at(0).type(),
QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
QVERIFY(info->contains(chars[0].descriptors().at(0)));
QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x62));
QCOMPARE(chars[0].descriptors().at(1).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[0].descriptors().at(1).type(),
QBluetoothUuid::CharacteristicUserDescription);
// value different in other revisions and test may fail
QCOMPARE(chars[0].descriptors().at(1).value(),
QByteArray::fromHex("4779726f2044617461"));
QVERIFY(info->contains(chars[0].descriptors().at(1)));
// Gyroscope Config
temp = QString("f000aa52-0451-4000-b000-000000000000");
QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x64));
QCOMPARE(chars[1].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
QCOMPARE(chars[1].value(), QByteArray::fromHex("00"));
QVERIFY(chars[1].isValid());
QVERIFY(info->contains(chars[1]));
QCOMPARE(chars[1].descriptors().count(), 1);
//descriptor checks
QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x65));
QCOMPARE(chars[1].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[1].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[1].descriptors().at(0).value(),
QByteArray::fromHex("4779726f20436f6e662e"));
QVERIFY(info->contains(chars[1].descriptors().at(0)));
// Gyroscope Period
temp = QString("f000aa53-0451-4000-b000-000000000000");
QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x67));
QCOMPARE(chars[2].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
QCOMPARE(chars[2].value(), QByteArray::fromHex("64"));
QVERIFY(chars[2].isValid());
QVERIFY(info->contains(chars[2]));
QCOMPARE(chars[2].descriptors().count(), 1);
//descriptor checks
QCOMPARE(chars[2].descriptors().at(0).isValid(), true);
HANDLE_COMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x68));
QCOMPARE(chars[2].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[2].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[2].descriptors().at(0).value(),
QByteArray::fromHex("4779726f20506572696f64"));
QVERIFY(info->contains(chars[2].descriptors().at(0)));
} else if (info->serviceUuid() ==
QBluetoothUuid(QString("f000aa60-0451-4000-b000-000000000000"))) {
qDebug() << "Verifying Test Service";
QList<QLowEnergyCharacteristic> chars = info->characteristics();
QCOMPARE(chars.count(), 2);
// Test Data
QString temp("f000aa61-0451-4000-b000-000000000000");
QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x70));
QCOMPARE(chars[0].properties(),
(QLowEnergyCharacteristic::Read));
QCOMPARE(chars[0].value(), QByteArray::fromHex("3f00"));
QVERIFY(chars[0].isValid());
QVERIFY(info->contains(chars[0]));
QCOMPARE(chars[0].descriptors().count(), 1);
QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x71));
QCOMPARE(chars[0].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[0].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[0].descriptors().at(0).value(),
QByteArray::fromHex("546573742044617461"));
QVERIFY(info->contains(chars[0].descriptors().at(0)));
// Test Config
temp = QString("f000aa62-0451-4000-b000-000000000000");
QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x73));
QCOMPARE(chars[1].properties(),
(QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
QCOMPARE(chars[1].value(), QByteArray::fromHex("00"));
QVERIFY(chars[1].isValid());
QVERIFY(info->contains(chars[1]));
QCOMPARE(chars[1].descriptors().count(), 1);
//descriptor checks
QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x74));
QCOMPARE(chars[1].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[1].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[1].descriptors().at(0).value(),
QByteArray::fromHex("5465737420436f6e666967"));
QVERIFY(info->contains(chars[1].descriptors().at(0)));
} else if (info->serviceUuid() ==
QBluetoothUuid(QString("f000ccc0-0451-4000-b000-000000000000"))) {
qDebug() << "Connection Control Service";
QList<QLowEnergyCharacteristic> chars = info->characteristics();
QCOMPARE(chars.count(), 3);
//first characteristic
QString temp("f000ccc1-0451-4000-b000-000000000000");
QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x77));
QCOMPARE(chars[0].properties(),
(QLowEnergyCharacteristic::Notify|QLowEnergyCharacteristic::Read));
// the connection control parameter change from platform to platform
// better not test them here
//QCOMPARE(chars[0].value(), QByteArray::fromHex("000000000000"));
QVERIFY(chars[0].isValid());
QVERIFY(info->contains(chars[0]));
QCOMPARE(chars[0].descriptors().count(), 2);
//descriptor checks
QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x78));
QCOMPARE(chars[0].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
QCOMPARE(chars[0].descriptors().at(0).type(),
QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
QVERIFY(info->contains(chars[0].descriptors().at(0)));
QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x79));
QCOMPARE(chars[0].descriptors().at(1).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[0].descriptors().at(1).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[0].descriptors().at(1).value(),
QByteArray::fromHex("436f6e6e2e20506172616d73"));
QVERIFY(info->contains(chars[0].descriptors().at(1)));
//second characteristic
temp = QString("f000ccc2-0451-4000-b000-000000000000");
QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x7b));
QCOMPARE(chars[1].properties(), QLowEnergyCharacteristic::Write);
QCOMPARE(chars[1].value(), QByteArray());
QVERIFY(chars[1].isValid());
QVERIFY(info->contains(chars[1]));
QCOMPARE(chars[1].descriptors().count(), 1);
QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x7c));
QCOMPARE(chars[1].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[1].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[1].descriptors().at(0).value(),
QByteArray::fromHex("436f6e6e2e20506172616d7320526571"));
QVERIFY(info->contains(chars[1].descriptors().at(0)));
//third characteristic
temp = QString("f000ccc3-0451-4000-b000-000000000000");
QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x7e));
QCOMPARE(chars[2].properties(), QLowEnergyCharacteristic::Write);
QCOMPARE(chars[2].value(), QByteArray());
QVERIFY(chars[2].isValid());
QVERIFY(info->contains(chars[2]));
QCOMPARE(chars[2].descriptors().count(), 1);
QCOMPARE(chars[2].descriptors().at(0).isValid(), true);
HANDLE_COMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x7f));
QCOMPARE(chars[2].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[2].descriptors().at(0).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[2].descriptors().at(0).value(),
QByteArray::fromHex("446973636f6e6e65637420526571"));
QVERIFY(info->contains(chars[2].descriptors().at(0)));
} else if (info->serviceUuid() ==
QBluetoothUuid(QString("f000ffc0-0451-4000-b000-000000000000"))) {
qDebug() << "Verifying OID Service";
QList<QLowEnergyCharacteristic> chars = info->characteristics();
QCOMPARE(chars.count(), 2);
// first characteristic
QString temp("f000ffc1-0451-4000-b000-000000000000");
QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x82));
QCOMPARE(chars[0].properties(),
(QLowEnergyCharacteristic::Notify|QLowEnergyCharacteristic::Write|QLowEnergyCharacteristic::WriteNoResponse));
QCOMPARE(chars[0].value(), QByteArray());
QVERIFY(chars[0].isValid());
QVERIFY(info->contains(chars[0]));
QCOMPARE(chars[0].descriptors().count(), 2);
//descriptor checks
QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x83));
QCOMPARE(chars[0].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
QCOMPARE(chars[0].descriptors().at(0).type(),
QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
QVERIFY(info->contains(chars[0].descriptors().at(0)));
QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x84));
QCOMPARE(chars[0].descriptors().at(1).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[0].descriptors().at(1).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[0].descriptors().at(1).value(),
QByteArray::fromHex("496d67204964656e74696679"));
QVERIFY(info->contains(chars[0].descriptors().at(1)));
// second characteristic
temp = QString("f000ffc2-0451-4000-b000-000000000000");
QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x86));
QCOMPARE(chars[1].properties(),
(QLowEnergyCharacteristic::Notify|QLowEnergyCharacteristic::Write|QLowEnergyCharacteristic::WriteNoResponse));
QCOMPARE(chars[1].value(), QByteArray());
QVERIFY(chars[1].isValid());
QVERIFY(info->contains(chars[1]));
QCOMPARE(chars[1].descriptors().count(), 2);
//descriptor checks
QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x87));
QCOMPARE(chars[1].descriptors().at(0).uuid(),
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
QCOMPARE(chars[1].descriptors().at(0).type(),
QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
QVERIFY(info->contains(chars[1].descriptors().at(0)));
QCOMPARE(chars[1].descriptors().at(1).isValid(), true);
// value different in other revisions and test may fail
HANDLE_COMPARE(chars[1].descriptors().at(1).handle(), QLowEnergyHandle(0x88));
QCOMPARE(chars[1].descriptors().at(1).uuid(),
QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
QCOMPARE(chars[1].descriptors().at(1).type(),
QBluetoothUuid::CharacteristicUserDescription);
QCOMPARE(chars[1].descriptors().at(1).value(),
QByteArray::fromHex("496d6720426c6f636b"));
QVERIFY(info->contains(chars[1].descriptors().at(1)));
} else {
QFAIL(QString("Service not found" + info->serviceUuid().toString()).toUtf8().constData());
}
}
/*
* CCC descriptors can have one of three distinct values:
* 0000 - notifications and indications are off
* 0100 - notifications enabled
* 0200 - indications enabled
*
* The exact value is managed by the BTLE peripheral for each central
* that connects. The value of this field is session based and may be retained
* during multiple connections.
*
* This function returns \c true if the CCC value has a valid range.
* */
bool tst_QLowEnergyController::verifyClientCharacteristicValue(const QByteArray &value)
{
if (value == QByteArray::fromHex("0000")
|| value == QByteArray::fromHex("0100")
|| value == QByteArray::fromHex("0200") )
return true;
qWarning() << "Found incorrect CC value" << value.toHex();
return false;
}
void tst_QLowEnergyController::tst_defaultBehavior()
{
QList<QBluetoothAddress> foundAddresses;
const QList<QBluetoothHostInfo> infos = QBluetoothLocalDevice::allDevices();
for (const QBluetoothHostInfo &info : infos)
foundAddresses.append(info.address());
const QBluetoothAddress randomAddress("11:22:33:44:55:66");
// Test automatic detection of local adapter
QLowEnergyController controlDefaultAdapter(randomAddress);
QCOMPARE(controlDefaultAdapter.remoteAddress(), randomAddress);
QCOMPARE(controlDefaultAdapter.state(), QLowEnergyController::UnconnectedState);
if (foundAddresses.isEmpty()) {
QVERIFY(controlDefaultAdapter.localAddress().isNull());
} else {
QCOMPARE(controlDefaultAdapter.error(), QLowEnergyController::NoError);
QVERIFY(controlDefaultAdapter.errorString().isEmpty());
QVERIFY(foundAddresses.contains(controlDefaultAdapter.localAddress()));
// unrelated uuids don't return valid service object
// invalid service uuid
QVERIFY(!controlDefaultAdapter.createServiceObject(
QBluetoothUuid()));
// some random uuid
QVERIFY(!controlDefaultAdapter.createServiceObject(
QBluetoothUuid(QBluetoothUuid::DeviceName)));
}
QCOMPARE(controlDefaultAdapter.services().count(), 0);
// Test explicit local adapter
if (!foundAddresses.isEmpty()) {
QLowEnergyController controlExplicitAdapter(randomAddress,
foundAddresses[0]);
QCOMPARE(controlExplicitAdapter.remoteAddress(), randomAddress);
QCOMPARE(controlExplicitAdapter.localAddress(), foundAddresses[0]);
QCOMPARE(controlExplicitAdapter.state(),
QLowEnergyController::UnconnectedState);
QCOMPARE(controlExplicitAdapter.services().count(), 0);
// unrelated uuids don't return valid service object
// invalid service uuid
QVERIFY(!controlExplicitAdapter.createServiceObject(
QBluetoothUuid()));
// some random uuid
QVERIFY(!controlExplicitAdapter.createServiceObject(
QBluetoothUuid(QBluetoothUuid::DeviceName)));
}
}
void tst_QLowEnergyController::tst_writeCharacteristic()
{
#if !defined(Q_OS_MACOS) && !QT_CONFIG(winrt_bt)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");
#endif
if (!remoteDeviceInfo.isValid())
QSKIP("No remote BTLE device found. Skipping test.");
QLowEnergyController control(remoteDeviceInfo);
QCOMPARE(control.error(), QLowEnergyController::NoError);
control.connectToDevice();
{
QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
30000);
}
if (control.state() == QLowEnergyController::ConnectingState
|| control.error() != QLowEnergyController::NoError) {
// default BTLE backend forever hangs in ConnectingState
QSKIP("Cannot connect to remote device");
}
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
control.discoverServices();
QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
QCOMPARE(stateSpy.count(), 2);
QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveringState);
QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveredState);
const QBluetoothUuid testService(QString("f000aa60-0451-4000-b000-000000000000"));
QList<QBluetoothUuid> uuids = control.services();
QVERIFY(uuids.contains(testService));
QLowEnergyService *service = control.createServiceObject(testService, this);
QVERIFY(service);
service->discoverDetails();
QTRY_VERIFY_WITH_TIMEOUT(
service->state() == QLowEnergyService::ServiceDiscovered, 30000);
// test service described by
// http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User%27s_Guide
const QList<QLowEnergyCharacteristic> chars = service->characteristics();
QLowEnergyCharacteristic dataChar;
QLowEnergyCharacteristic configChar;
for (int i = 0; i < chars.count(); i++) {
if (chars[i].uuid() == QBluetoothUuid(QString("f000aa61-0451-4000-b000-000000000000")))
dataChar = chars[i];
else if (chars[i].uuid() == QBluetoothUuid(QString("f000aa62-0451-4000-b000-000000000000")))
configChar = chars[i];
}
QVERIFY(dataChar.isValid());
QVERIFY(!(dataChar.properties() & ~QLowEnergyCharacteristic::Read)); // only a read char
QVERIFY(service->contains(dataChar));
QVERIFY(configChar.isValid());
QVERIFY(configChar.properties() & QLowEnergyCharacteristic::Write);
QVERIFY(configChar.properties() & QLowEnergyCharacteristic::Read);
QVERIFY(service->contains(configChar));
QCOMPARE(dataChar.value(), QByteArray::fromHex("3f00"));
QVERIFY(configChar.value() == QByteArray::fromHex("00")
|| configChar.value() == QByteArray::fromHex("81"));
QSignalSpy writeSpy(service,
SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)));
QSignalSpy readSpy(service,
SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)));
// *******************************************
// test writing of characteristic
// enable Blinking LED if not already enabled
if (configChar.value() != QByteArray::fromHex("81")) {
service->writeCharacteristic(configChar, QByteArray::fromHex("81")); //0x81 blink LED D1
QTRY_VERIFY_WITH_TIMEOUT(!writeSpy.isEmpty(), 10000);
QCOMPARE(configChar.value(), QByteArray::fromHex("81"));
QList<QVariant> firstSignalData = writeSpy.first();
QLowEnergyCharacteristic signalChar = firstSignalData[0].value<QLowEnergyCharacteristic>();
QByteArray signalValue = firstSignalData[1].toByteArray();
QCOMPARE(signalValue, QByteArray::fromHex("81"));
QVERIFY(signalChar == configChar);
writeSpy.clear();
}
// test direct read of configChar
QVERIFY(readSpy.isEmpty());
service->readCharacteristic(configChar);
QTRY_VERIFY_WITH_TIMEOUT(!readSpy.isEmpty(), 10000);
QCOMPARE(configChar.value(), QByteArray::fromHex("81"));
QCOMPARE(readSpy.count(), 1); //expect one characteristicRead signal
{
//verify the readCharacteristic()
QList<QVariant> firstSignalData = readSpy.first();
QLowEnergyCharacteristic signalChar = firstSignalData[0].value<QLowEnergyCharacteristic>();
QByteArray signalValue = firstSignalData[1].toByteArray();
QCOMPARE(signalValue, QByteArray::fromHex("81"));
QCOMPARE(signalValue, configChar.value());
QVERIFY(signalChar == configChar);
}
service->writeCharacteristic(configChar, QByteArray::fromHex("00")); //turn LED D1 off
QTRY_VERIFY_WITH_TIMEOUT(!writeSpy.isEmpty(), 10000);
QCOMPARE(configChar.value(), QByteArray::fromHex("00"));
QList<QVariant> firstSignalData = writeSpy.first();
QLowEnergyCharacteristic signalChar = firstSignalData[0].value<QLowEnergyCharacteristic>();
QByteArray signalValue = firstSignalData[1].toByteArray();
QCOMPARE(signalValue, QByteArray::fromHex("00"));
QVERIFY(signalChar == configChar);
// *******************************************
// write wrong value -> error response required
QSignalSpy errorSpy(service, SIGNAL(error(QLowEnergyService::ServiceError)));
writeSpy.clear();
QCOMPARE(errorSpy.count(), 0);
QCOMPARE(writeSpy.count(), 0);
// write 2 byte value to 1 byte characteristic
service->writeCharacteristic(configChar, QByteArray::fromHex("1111"));
QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 10000);
QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::CharacteristicWriteError);
QCOMPARE(service->error(), QLowEnergyService::CharacteristicWriteError);
QCOMPARE(writeSpy.count(), 0);
QCOMPARE(configChar.value(), QByteArray::fromHex("00"));
// *******************************************
// write to read-only characteristic -> error
errorSpy.clear();
QCOMPARE(errorSpy.count(), 0);
service->writeCharacteristic(dataChar, QByteArray::fromHex("ffff"));
QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 10000);
QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::CharacteristicWriteError);
QCOMPARE(service->error(), QLowEnergyService::CharacteristicWriteError);
QCOMPARE(writeSpy.count(), 0);
QCOMPARE(dataChar.value(), QByteArray::fromHex("3f00"));
control.disconnectFromDevice();
// *******************************************
// write value while disconnected -> error
errorSpy.clear();
QCOMPARE(errorSpy.count(), 0);
service->writeCharacteristic(configChar, QByteArray::fromHex("ffff"));
QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 2000);
QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::OperationError);
QCOMPARE(service->error(), QLowEnergyService::OperationError);
QCOMPARE(writeSpy.count(), 0);
QCOMPARE(configChar.value(), QByteArray::fromHex("00"));
// invalid characteristics still belong to their respective service
QVERIFY(service->contains(configChar));
QVERIFY(service->contains(dataChar));
QVERIFY(!service->contains(QLowEnergyCharacteristic()));
delete service;
}
void tst_QLowEnergyController::tst_readWriteDescriptor()
{
#if !defined(Q_OS_MACOS) && !QT_CONFIG(winrt_bt)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");
#endif
if (!remoteDeviceInfo.isValid())
QSKIP("No remote BTLE device found. Skipping test.");
QLowEnergyController control(remoteDeviceInfo);
// quick setup - more elaborate test is done by connect()
control.connectToDevice();
{
QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
30000);
}
if (control.state() == QLowEnergyController::ConnectingState
|| control.error() != QLowEnergyController::NoError) {
// default BTLE backend forever hangs in ConnectingState
QSKIP("Cannot connect to remote device");
}
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
control.discoverServices();
QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
QCOMPARE(stateSpy.count(), 2);
QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveringState);
QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveredState);
const QBluetoothUuid testService(QString("f000aa00-0451-4000-b000-000000000000"));
QList<QBluetoothUuid> uuids = control.services();
QVERIFY(uuids.contains(testService));
QLowEnergyService *service = control.createServiceObject(testService, this);
QVERIFY(service);
service->discoverDetails();
QTRY_VERIFY_WITH_TIMEOUT(
service->state() == QLowEnergyService::ServiceDiscovered, 30000);
// Temperature service described by
// http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User%27s_Guide
// 1. Find temperature data characteristic
const QLowEnergyCharacteristic tempData = service->characteristic(
QBluetoothUuid(QStringLiteral("f000aa01-0451-4000-b000-000000000000")));
const QLowEnergyCharacteristic tempConfig = service->characteristic(
QBluetoothUuid(QStringLiteral("f000aa02-0451-4000-b000-000000000000")));
if (!tempData.isValid()) {
delete service;
control.disconnectFromDevice();
QSKIP("Cannot find temperature data characteristic of TI Sensor");
}
// 2. Find temperature data notification descriptor
const QLowEnergyDescriptor notification = tempData.descriptor(
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
if (!notification.isValid()) {
delete service;
control.disconnectFromDevice();
QSKIP("Cannot find temperature data notification of TI Sensor");
}
QCOMPARE(notification.value(), QByteArray::fromHex("0000"));
QVERIFY(service->contains(notification));
QVERIFY(service->contains(tempData));
if (tempConfig.isValid()) {
QVERIFY(service->contains(tempConfig));
QCOMPARE(tempConfig.value(), QByteArray::fromHex("00"));
}
// 3. Test reading and writing to descriptor -> activate notifications
QSignalSpy descWrittenSpy(service,
SIGNAL(descriptorWritten(QLowEnergyDescriptor,QByteArray)));
QSignalSpy descReadSpy(service,
SIGNAL(descriptorRead(QLowEnergyDescriptor,QByteArray)));
QSignalSpy charWrittenSpy(service,
SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)));
QSignalSpy charChangedSpy(service,
SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)));
QLowEnergyDescriptor signalDesc;
QList<QVariant> firstSignalData;
QByteArray signalValue;
if (notification.value() != QByteArray::fromHex("0100")) {
// enable notifications if not already done
service->writeDescriptor(notification, QByteArray::fromHex("0100"));
QTRY_VERIFY_WITH_TIMEOUT(!descWrittenSpy.isEmpty(), 3000);
QCOMPARE(notification.value(), QByteArray::fromHex("0100"));
firstSignalData = descWrittenSpy.first();
signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>();
signalValue = firstSignalData[1].toByteArray();
QCOMPARE(signalValue, QByteArray::fromHex("0100"));
QVERIFY(notification == signalDesc);
descWrittenSpy.clear();
}
// 4. Test reception of notifications
// activate the temperature sensor if available
if (tempConfig.isValid()) {
service->writeCharacteristic(tempConfig, QByteArray::fromHex("01"));
// first signal is confirmation of tempConfig write
// subsequent signals are temp data updates
QTRY_VERIFY_WITH_TIMEOUT(charWrittenSpy.count() == 1, 10000);
QTRY_VERIFY_WITH_TIMEOUT(charChangedSpy.count() >= 4, 10000);
QCOMPARE(charWrittenSpy.count(), 1);
QLowEnergyCharacteristic writtenChar = charWrittenSpy[0].at(0).value<QLowEnergyCharacteristic>();
QByteArray writtenValue = charWrittenSpy[0].at(1).toByteArray();
QCOMPARE(tempConfig, writtenChar);
QCOMPARE(tempConfig.value(), writtenValue);
QCOMPARE(writtenChar.value(), writtenValue);
QCOMPARE(writtenValue, QByteArray::fromHex("01"));
QList<QVariant> entry;
for (int i = 0; i < charChangedSpy.count(); i++) {
entry = charChangedSpy[i];
const QLowEnergyCharacteristic ch = entry[0].value<QLowEnergyCharacteristic>();
QCOMPARE(tempData, ch);
//check last characteristic changed value matches the characteristics current value
if (i == (charChangedSpy.count() - 1)) {
writtenValue = entry[1].toByteArray();
QCOMPARE(ch.value(), writtenValue);
QCOMPARE(tempData.value(), writtenValue);
}
}
service->writeCharacteristic(tempConfig, QByteArray::fromHex("00"));
}
// 5. Test reading and writing of/to descriptor -> deactivate notifications
service->readDescriptor(notification);
QTRY_VERIFY_WITH_TIMEOUT(!descReadSpy.isEmpty(), 3000);
QCOMPARE(descReadSpy.count(), 1);
firstSignalData = descReadSpy.first();
signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>();
signalValue = firstSignalData[1].toByteArray();
QCOMPARE(signalValue, notification.value());
QCOMPARE(notification.value(), QByteArray::fromHex("0100"));
descReadSpy.clear();
service->writeDescriptor(notification, QByteArray::fromHex("0000"));
// verify
QTRY_VERIFY_WITH_TIMEOUT(!descWrittenSpy.isEmpty(), 3000);
QCOMPARE(notification.value(), QByteArray::fromHex("0000"));
firstSignalData = descWrittenSpy.first();
signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>();
signalValue = firstSignalData[1].toByteArray();
QCOMPARE(signalValue, QByteArray::fromHex("0000"));
QVERIFY(notification == signalDesc);
descWrittenSpy.clear();
// The series of wait calls below is required because toggling CCC via the notifying
// property consistently crashes BlueZ 5.47. BlueZ 5.48 does not crash but
// an error is thrown. For details see QTBUG-65729
if (isBluezDbusLE)
QTest::qWait(1000);
// test concurrent writeRequests
// they need to be queued up
service->writeDescriptor(notification,QByteArray::fromHex("0100"));
if (isBluezDbusLE)
QTest::qWait(1000);
service->writeDescriptor(notification, QByteArray::fromHex("0000"));
if (isBluezDbusLE)
QTest::qWait(1000);
service->writeDescriptor(notification, QByteArray::fromHex("0100"));
if (isBluezDbusLE)
QTest::qWait(1000);
service->writeDescriptor(notification, QByteArray::fromHex("0000"));
if (isBluezDbusLE)
QTest::qWait(1000);
QTRY_VERIFY_WITH_TIMEOUT(descWrittenSpy.count() == 4, 10000);
QCOMPARE(notification.value(), QByteArray::fromHex("0000"));
for (int i = 0; i < descWrittenSpy.count(); i++) {
firstSignalData = descWrittenSpy.at(i);
signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>();
signalValue = firstSignalData[1].toByteArray();
if (i & 0x1) // odd
QCOMPARE(signalValue, QByteArray::fromHex("0000"));
else // even
QCOMPARE(signalValue, QByteArray::fromHex("0100"));
QVERIFY(notification == signalDesc);
}
// 5. Test reading and writing of/to descriptor -> deactivate notifications
service->readDescriptor(notification);
QTRY_VERIFY_WITH_TIMEOUT(!descReadSpy.isEmpty(), 3000);
QCOMPARE(descReadSpy.count(), 1);
firstSignalData = descReadSpy.first();
signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>();
signalValue = firstSignalData[1].toByteArray();
QCOMPARE(signalValue, notification.value());
QCOMPARE(notification.value(), QByteArray::fromHex("0000"));
descReadSpy.clear();
descWrittenSpy.clear();
// *******************************************
// write wrong value -> error response required
QSignalSpy errorSpy(service, SIGNAL(error(QLowEnergyService::ServiceError)));
descWrittenSpy.clear();
QCOMPARE(errorSpy.count(), 0);
QCOMPARE(descWrittenSpy.count(), 0);
// write 4 byte value to 2 byte characteristic
service->writeDescriptor(notification, QByteArray::fromHex("11112222"));
#ifdef Q_OS_MAC
// On OS X/iOS we have a special method to set notify value,
// it accepts only false/true and not
// writing descriptors, there is only one way to find this error -
// immediately intercept in LE controller and set the error.
QVERIFY(!errorSpy.isEmpty());
#else
QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 30000);
#endif
QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::DescriptorWriteError);
QCOMPARE(service->error(), QLowEnergyService::DescriptorWriteError);
QCOMPARE(descWrittenSpy.count(), 0);
QCOMPARE(notification.value(), QByteArray::fromHex("0000"));
control.disconnectFromDevice();
// *******************************************
// write value while disconnected -> error
errorSpy.clear();
service->writeDescriptor(notification, QByteArray::fromHex("0100"));
QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 2000);
QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::OperationError);
QCOMPARE(service->error(), QLowEnergyService::OperationError);
QCOMPARE(descWrittenSpy.count(), 0);
QCOMPARE(notification.value(), QByteArray::fromHex("0000"));
delete service;
}
/*
* By default this test is skipped.
*
* Following tests are performed:
* - encrypted read and discovery
* - readCharacteristic() of values longer than MTU
* - readCharacteristic() if values equal to MTU
*
* This test is semi manual as the test device environment is very specific.
* A programmable BTLE device is required. Currently, the test requires
* the CSR Dev Kit using the hr_sensor example.
*
* The following changes must be done to example to be able to fully
* utilise the test:
* 1.) gap_service_db.db -> UUID_DEVICE_NAME char - add FLAG_ENCR_R
* => tests encrypted read/discovery
* 2.) dev_info_service_db.db -> UUID_DEVICE_INFO_MANUFACTURER_NAME
* => The default name "Cambridge Silicon Radio" must be changed
* to "Cambridge Silicon Radi" (new length 22)
* 3.) revert change 1 above and redo test. This attempts to write a
* char that is readable w/o encryption but writeable with encryption
* => tests encryption code lines in writeCharacteristic()
* => otherwise the read encryption would have increased security level already
* => programmable CSR device must be reset before each run of this test
* (to undo the previous write)
*/
void tst_QLowEnergyController::tst_customProgrammableDevice()
{
QSKIP("Skipping encryption");
//Adjust the uuids and device address as see fit to match
//values that match the current test environment
//The target characteristic must be readble and writable
//under encryption to test dynamic switching of security level
QBluetoothAddress encryptedDevice(QString("00:02:5B:00:15:10"));
QBluetoothUuid serviceUuid(QBluetoothUuid::GenericAccess);
QBluetoothUuid characterristicUuid(QBluetoothUuid::DeviceName);
QLowEnergyController control(encryptedDevice);
QCOMPARE(control.error(), QLowEnergyController::NoError);
control.connectToDevice();
{
QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
30000);
}
if (control.state() == QLowEnergyController::ConnectingState
|| control.error() != QLowEnergyController::NoError) {
// default BTLE backend forever hangs in ConnectingState
QSKIP("Cannot connect to remote device");
}
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
control.discoverServices();
QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
QCOMPARE(stateSpy.count(), 2);
QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveringState);
QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveredState);
QList<QBluetoothUuid> uuids = control.services();
QVERIFY(uuids.contains(serviceUuid));
QLowEnergyService *service = control.createServiceObject(serviceUuid, this);
QVERIFY(service);
// 1.) discovery triggers read of device name char which is encrypted
service->discoverDetails();
QTRY_VERIFY_WITH_TIMEOUT(
service->state() == QLowEnergyService::ServiceDiscovered, 30000);
QLowEnergyCharacteristic encryptedChar = service->characteristic(
characterristicUuid);
const QByteArray encryptedReference("CSR HR Sensor");
QVERIFY(encryptedChar.isValid());
QCOMPARE(encryptedChar.value(), encryptedReference);
// 2.) read of encrypted characteristic
// => the discovery of the encrypted char above will have switched to
// encryption already.
QSignalSpy encryptedReadSpy(service,
SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)));
QSignalSpy encryptedErrorSpy(service,
SIGNAL(error(QLowEnergyService::ServiceError)));
service->readCharacteristic(encryptedChar);
QTRY_VERIFY_WITH_TIMEOUT(!encryptedReadSpy.isEmpty(), 10000);
QVERIFY(encryptedErrorSpy.isEmpty());
QCOMPARE(encryptedReadSpy.count(), 1);
QList<QVariant> entry = encryptedReadSpy[0];
QVERIFY(entry[0].value<QLowEnergyCharacteristic>() == encryptedChar);
QCOMPARE(entry[1].toByteArray(), encryptedReference);
QCOMPARE(encryptedChar.value(), encryptedReference);
// 3.) write to encrypted characteristic
QSignalSpy encryptedWriteSpy(service,
SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)));
encryptedReadSpy.clear();
encryptedErrorSpy.clear();
const QByteArray newValue("ZZZ HR Sensor");
service->writeCharacteristic(encryptedChar, newValue);
QTRY_VERIFY_WITH_TIMEOUT(!encryptedWriteSpy.isEmpty(), 10000);
QVERIFY(encryptedErrorSpy.isEmpty());
QVERIFY(encryptedReadSpy.isEmpty());
QCOMPARE(encryptedWriteSpy.count(), 1);
entry = encryptedWriteSpy[0];
QVERIFY(entry[0].value<QLowEnergyCharacteristic>() == encryptedChar);
QCOMPARE(entry[1].toByteArray(), newValue);
QCOMPARE(encryptedChar.value(), newValue);
delete service;
//change to Device Information service
QVERIFY(uuids.contains(QBluetoothUuid::DeviceInformation));
service = control.createServiceObject(QBluetoothUuid::DeviceInformation);
QVERIFY(service);
service->discoverDetails();
QTRY_VERIFY_WITH_TIMEOUT(
service->state() == QLowEnergyService::ServiceDiscovered, 30000);
// 4.) read of software revision string which is longer than mtu
// tests readCharacteristic() including blob reads
QSignalSpy readSpy(service,
SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)));
QSignalSpy errorSpy(service,
SIGNAL(error(QLowEnergyService::ServiceError)));
const QByteArray expectedSoftRev("Application version 2.3.0.0");
QLowEnergyCharacteristic softwareRevChar
= service->characteristic(QBluetoothUuid::SoftwareRevisionString);
QVERIFY(softwareRevChar.isValid());
QCOMPARE(softwareRevChar.value(), expectedSoftRev);
service->readCharacteristic(softwareRevChar);
QTRY_VERIFY_WITH_TIMEOUT(!readSpy.isEmpty(), 10000);
QVERIFY(errorSpy.isEmpty());
QCOMPARE(readSpy.count(), 1);
entry = readSpy[0];
QVERIFY(entry[0].value<QLowEnergyCharacteristic>() == softwareRevChar);
QCOMPARE(entry[1].toByteArray(), expectedSoftRev);
QCOMPARE(softwareRevChar.value(), expectedSoftRev);
// 5.) read of manufacturer string which is exactly as long as single
// MTU size (assuming negotiated MTU is 23)
// => blob read test without blob being required
// => the read blob answer will have zero length
readSpy.clear();
// This assumes the manufacturer string was mondified via CSR SDK
// see function description above
const QByteArray expectedManufacturer("Cambridge Silicon Radi");
QLowEnergyCharacteristic manufacturerChar = service->characteristic(
QBluetoothUuid::ManufacturerNameString);
QVERIFY(manufacturerChar.isValid());
QCOMPARE(manufacturerChar.value(), expectedManufacturer);
service->readCharacteristic(manufacturerChar);
QTRY_VERIFY_WITH_TIMEOUT(!readSpy.isEmpty(), 10000);
QVERIFY(errorSpy.isEmpty());
QCOMPARE(readSpy.count(), 1);
entry = readSpy[0];
QVERIFY(entry[0].value<QLowEnergyCharacteristic>() == manufacturerChar);
QCOMPARE(entry[1].toByteArray(), expectedManufacturer);
QCOMPARE(manufacturerChar.value(), expectedManufacturer);
delete service;
control.disconnectFromDevice();
}
/* 1.) Test with undiscovered devices
- read and write invalid char
2.) Test with discovered devices
- read non-readable char
- write non-writable char
*/
void tst_QLowEnergyController::tst_errorCases()
{
#if !defined(Q_OS_MACOS) && !QT_CONFIG(winrt_bt)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");
#endif
if (!remoteDeviceInfo.isValid())
QSKIP("No remote BTLE device found. Skipping test.");
QLowEnergyController control(remoteDeviceInfo);
QCOMPARE(control.error(), QLowEnergyController::NoError);
control.connectToDevice();
{
QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
30000);
}
if (control.state() == QLowEnergyController::ConnectingState
|| control.error() != QLowEnergyController::NoError) {
// default BTLE backend forever hangs in ConnectingState
QSKIP("Cannot connect to remote device");
}
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
control.discoverServices();
QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
QCOMPARE(stateSpy.count(), 2);
QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveringState);
QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveredState);
// Setup required uuids
const QBluetoothUuid irTemperaturServiceUuid(QStringLiteral("f000aa00-0451-4000-b000-000000000000"));
const QBluetoothUuid irCharUuid(QString("f000aa01-0451-4000-b000-000000000000"));
const QBluetoothUuid oadServiceUuid(QStringLiteral("f000ffc0-0451-4000-b000-000000000000"));
const QBluetoothUuid oadCharUuid(QString("f000ffc1-0451-4000-b000-000000000000"));
QVERIFY(control.services().contains(irTemperaturServiceUuid));
QVERIFY(control.services().contains(oadServiceUuid));
// Create service objects and basic tests
QLowEnergyService *irService = control.createServiceObject(irTemperaturServiceUuid);
QVERIFY(irService);
QCOMPARE(irService->state(), QLowEnergyService::DiscoveryRequired);
QVERIFY(irService->characteristics().isEmpty());
QLowEnergyService *oadService = control.createServiceObject(oadServiceUuid);
QVERIFY(oadService);
QCOMPARE(oadService->state(), QLowEnergyService::DiscoveryRequired);
QVERIFY(oadService->characteristics().isEmpty());
QLowEnergyCharacteristic invalidChar;
QLowEnergyDescriptor invalidDesc;
QVERIFY(!irService->contains(invalidChar));
QVERIFY(!irService->contains(invalidDesc));
QSignalSpy irErrorSpy(irService, SIGNAL(error(QLowEnergyService::ServiceError)));
QSignalSpy oadErrorSpy(oadService, SIGNAL(error(QLowEnergyService::ServiceError)));
QSignalSpy irReadSpy(irService, SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)));
QSignalSpy irWrittenSpy(irService, SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)));
QSignalSpy irDescReadSpy(irService, SIGNAL(descriptorRead(QLowEnergyDescriptor,QByteArray)));
QSignalSpy irDescWrittenSpy(irService, SIGNAL(descriptorWritten(QLowEnergyDescriptor,QByteArray)));
QSignalSpy oadCharReadSpy(oadService, SIGNAL(descriptorRead(QLowEnergyDescriptor,QByteArray)));
// ********************************************************
// Test read/write to discovered service
// with invalid characteristic & descriptor
// discover IR Service
irService->discoverDetails();
QTRY_VERIFY_WITH_TIMEOUT(
irService->state() == QLowEnergyService::ServiceDiscovered, 30000);
QVERIFY(!irService->contains(invalidChar));
QVERIFY(!irService->contains(invalidDesc));
irErrorSpy.clear();
// read invalid characteristic
irService->readCharacteristic(invalidChar);
QTRY_VERIFY_WITH_TIMEOUT(!irErrorSpy.isEmpty(), 5000);
QCOMPARE(irErrorSpy.count(), 1);
QVERIFY(irWrittenSpy.isEmpty());
QVERIFY(irReadSpy.isEmpty());
QCOMPARE(irErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::OperationError);
irErrorSpy.clear();
// read invalid descriptor
irService->readDescriptor(invalidDesc);
QTRY_VERIFY_WITH_TIMEOUT(!irErrorSpy.isEmpty(), 5000);
QCOMPARE(irErrorSpy.count(), 1);
QVERIFY(irDescWrittenSpy.isEmpty());
QVERIFY(irDescReadSpy.isEmpty());
QCOMPARE(irErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::OperationError);
irErrorSpy.clear();
// write invalid characteristic
irService->writeCharacteristic(invalidChar, QByteArray("foo"));
QTRY_VERIFY_WITH_TIMEOUT(!irErrorSpy.isEmpty(), 5000);
QCOMPARE(irErrorSpy.count(), 1);
QVERIFY(irWrittenSpy.isEmpty());
QVERIFY(irReadSpy.isEmpty());
QCOMPARE(irErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::OperationError);
irErrorSpy.clear();
// write invalid descriptor
irService->readDescriptor(invalidDesc);
QTRY_VERIFY_WITH_TIMEOUT(!irErrorSpy.isEmpty(), 5000);
QCOMPARE(irErrorSpy.count(), 1);
QVERIFY(irDescWrittenSpy.isEmpty());
QVERIFY(irDescReadSpy.isEmpty());
QCOMPARE(irErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::OperationError);
irErrorSpy.clear();
// ********************************************************
// Test read/write to undiscovered service
// with invalid characteristic & descriptor
// read invalid characteristic
oadService->readCharacteristic(invalidChar);
QTRY_VERIFY_WITH_TIMEOUT(!oadErrorSpy.isEmpty(), 5000);
QCOMPARE(oadErrorSpy.count(), 1);
QCOMPARE(oadErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::OperationError);
oadErrorSpy.clear();
// read invalid descriptor
oadService->readDescriptor(invalidDesc);
QTRY_VERIFY_WITH_TIMEOUT(!oadErrorSpy.isEmpty(), 5000);
QCOMPARE(oadErrorSpy.count(), 1);
QCOMPARE(oadErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::OperationError);
oadErrorSpy.clear();
// write invalid characteristic
oadService->writeCharacteristic(invalidChar, QByteArray("foo"));
QTRY_VERIFY_WITH_TIMEOUT(!oadErrorSpy.isEmpty(), 5000);
QCOMPARE(oadErrorSpy.count(), 1);
QCOMPARE(oadErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::OperationError);
oadErrorSpy.clear();
// write invalid descriptor
oadService->readDescriptor(invalidDesc);
QTRY_VERIFY_WITH_TIMEOUT(!oadErrorSpy.isEmpty(), 5000);
QCOMPARE(oadErrorSpy.count(), 1);
QCOMPARE(oadErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::OperationError);
oadErrorSpy.clear();
// ********************************************************
// Write to non-writable char
QLowEnergyCharacteristic nonWritableChar = irService->characteristic(irCharUuid);
QVERIFY(nonWritableChar.isValid());
// not writeable in any form
QVERIFY(!(nonWritableChar.properties()
& (QLowEnergyCharacteristic::Write|QLowEnergyCharacteristic::WriteNoResponse
|QLowEnergyCharacteristic::WriteSigned)));
irService->writeCharacteristic(nonWritableChar, QByteArray("ABCD"));
QTRY_VERIFY_WITH_TIMEOUT(!irErrorSpy.isEmpty(), 5000);
QVERIFY(irWrittenSpy.isEmpty());
QVERIFY(irReadSpy.isEmpty());
QCOMPARE(irErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::CharacteristicWriteError);
irErrorSpy.clear();
// ********************************************************
// Write to non-writable desc
// CharacteristicUserDescription is not writable
QLowEnergyDescriptor nonWritableDesc = nonWritableChar.descriptor(
QBluetoothUuid::CharacteristicUserDescription);
QVERIFY(nonWritableDesc.isValid());
irService->writeDescriptor(nonWritableDesc, QByteArray("ABCD"));
QTRY_VERIFY_WITH_TIMEOUT(!irErrorSpy.isEmpty(), 5000);
QVERIFY(irWrittenSpy.isEmpty());
QVERIFY(irReadSpy.isEmpty());
QCOMPARE(irErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::DescriptorWriteError);
irErrorSpy.clear();
// ********************************************************
// Read non-readable char
// discover OAD Service
oadService->discoverDetails();
QTRY_VERIFY_WITH_TIMEOUT(
oadService->state() == QLowEnergyService::ServiceDiscovered, 30000);
oadErrorSpy.clear();
// Test reading
QLowEnergyCharacteristic oadChar = oadService->characteristic(oadCharUuid);
QVERIFY(oadChar.isValid());
oadService->readCharacteristic(oadChar);
QTRY_VERIFY_WITH_TIMEOUT(!oadErrorSpy.isEmpty(), 5000);
QCOMPARE(oadErrorSpy.count(), 1);
QVERIFY(oadCharReadSpy.isEmpty());
QCOMPARE(oadErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
QLowEnergyService::CharacteristicReadError);
oadErrorSpy.clear();
delete irService;
delete oadService;
control.disconnectFromDevice();
}
/*
Tests write without responses. We utilize the Over-The-Air image update
service of the SensorTag.
*/
void tst_QLowEnergyController::tst_writeCharacteristicNoResponse()
{
#if !defined(Q_OS_MACOS) && !QT_CONFIG(winrt_bt)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");
#endif
if (!remoteDeviceInfo.isValid())
QSKIP("No remote BTLE device found. Skipping test.");
QLowEnergyController control(remoteDeviceInfo);
QCOMPARE(control.error(), QLowEnergyController::NoError);
control.connectToDevice();
{
QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
30000);
}
if (control.state() == QLowEnergyController::ConnectingState
|| control.error() != QLowEnergyController::NoError) {
// default BTLE backend forever hangs in ConnectingState
QSKIP("Cannot connect to remote device");
}
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
control.discoverServices();
QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
QCOMPARE(stateSpy.count(), 2);
QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveringState);
QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
QLowEnergyController::DiscoveredState);
// The Over-The-Air update service uuid
const QBluetoothUuid testService(QString("f000ffc0-0451-4000-b000-000000000000"));
QList<QBluetoothUuid> uuids = control.services();
QVERIFY(uuids.contains(testService));
QLowEnergyService *service = control.createServiceObject(testService, this);
QVERIFY(service);
service->discoverDetails();
QTRY_VERIFY_WITH_TIMEOUT(
service->state() == QLowEnergyService::ServiceDiscovered, 30000);
// 1. Get "Image Identity" and "Image Block" characteristic
const QLowEnergyCharacteristic imageIdentityChar = service->characteristic(
QBluetoothUuid(QString("f000ffc1-0451-4000-b000-000000000000")));
const QLowEnergyCharacteristic imageBlockChar = service->characteristic(
QBluetoothUuid(QString("f000ffc2-0451-4000-b000-000000000000")));
QVERIFY(imageIdentityChar.isValid());
QVERIFY(imageIdentityChar.properties() & QLowEnergyCharacteristic::Write);
QVERIFY(imageIdentityChar.properties() & QLowEnergyCharacteristic::WriteNoResponse);
QVERIFY(!(imageIdentityChar.properties() & QLowEnergyCharacteristic::Read)); //not readable
QVERIFY(imageBlockChar.isValid());
// 2. Get "Image Identity" notification descriptor
const QLowEnergyDescriptor identityNotification = imageIdentityChar.descriptor(
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
const QLowEnergyDescriptor blockNotification = imageBlockChar.descriptor(
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
if (!identityNotification.isValid()
|| !blockNotification.isValid()
|| !imageIdentityChar.isValid()) {
delete service;
control.disconnectFromDevice();
QSKIP("Cannot find OAD char/notification");
}
// 3. Enable notifications
QSignalSpy descWrittenSpy(service,
SIGNAL(descriptorWritten(QLowEnergyDescriptor,QByteArray)));
QSignalSpy charChangedSpy(service,
SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)));
QSignalSpy charWrittenSpy(service,
SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)));
QSignalSpy charReadSpy(service,
SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)));
QSignalSpy errorSpy(service,
SIGNAL(error(QLowEnergyService::ServiceError)));
//enable notifications on both characteristics
if (identityNotification.value() != QByteArray::fromHex("0100")) {
service->writeDescriptor(identityNotification, QByteArray::fromHex("0100"));
QTRY_VERIFY_WITH_TIMEOUT(!descWrittenSpy.isEmpty(), 3000);
QCOMPARE(identityNotification.value(), QByteArray::fromHex("0100"));
QList<QVariant> firstSignalData = descWrittenSpy.first();
QLowEnergyDescriptor signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>();
QByteArray signalValue = firstSignalData[1].toByteArray();
QCOMPARE(signalValue, QByteArray::fromHex("0100"));
QVERIFY(identityNotification == signalDesc);
descWrittenSpy.clear();
}
if (blockNotification.value() != QByteArray::fromHex("0100")) {
service->writeDescriptor(blockNotification, QByteArray::fromHex("0100"));
QTRY_VERIFY_WITH_TIMEOUT(!descWrittenSpy.isEmpty(), 3000);
QCOMPARE(blockNotification.value(), QByteArray::fromHex("0100"));
QList<QVariant> firstSignalData = descWrittenSpy.first();
QLowEnergyDescriptor signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>();
QByteArray signalValue = firstSignalData[1].toByteArray();
QCOMPARE(signalValue, QByteArray::fromHex("0100"));
QVERIFY(blockNotification == signalDesc);
descWrittenSpy.clear();
}
QList<QVariant> entry;
// Test direct read of non-readable characteristic
QVERIFY(errorSpy.isEmpty());
QVERIFY(charReadSpy.isEmpty());
service->readCharacteristic(imageIdentityChar);
QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 10000);
QCOMPARE(errorSpy.count(), 1); // should throw CharacteristicReadError
QVERIFY(charReadSpy.isEmpty());
entry = errorSpy[0];
QCOMPARE(entry[0].value<QLowEnergyService::ServiceError>(),
QLowEnergyService::CharacteristicReadError);
// 4. Trigger image identity announcement (using traditional write)
bool foundOneImage = false;
// Image A
// Write triggers a notification and write confirmation
service->writeCharacteristic(imageIdentityChar, QByteArray::fromHex("0"));
QTest::qWait(1000);
QTRY_COMPARE_WITH_TIMEOUT(charChangedSpy.count(), 1, 5000);
QTRY_COMPARE_WITH_TIMEOUT(charWrittenSpy.count(), 1, 5000);
// This is very SensorTag specific logic.
// If the image block is empty the current firmware
// does not even send a notification for imageIdentityChar
// but for imageBlockChar
entry = charChangedSpy[0];
QLowEnergyCharacteristic first = entry[0].value<QLowEnergyCharacteristic>();
QByteArray val1 = entry[1].toByteArray();
if (val1.size() == 8) {
QCOMPARE(imageIdentityChar, first);
foundOneImage = true;
} else {
// we received a notification for imageBlockChar
QCOMPARE(imageBlockChar, first);
qWarning() << "Invalid image A ident info";
}
entry = charWrittenSpy[0];
QLowEnergyCharacteristic second = entry[0].value<QLowEnergyCharacteristic>();
QByteArray val2 = entry[1].toByteArray();
QCOMPARE(imageIdentityChar, second);
QVERIFY(val2 == QByteArray::fromHex("0") || val2 == val1);
// notifications on non-readable characteristics do not update cache
QVERIFY(imageIdentityChar.value().isEmpty());
QVERIFY(imageBlockChar.value().isEmpty());
charChangedSpy.clear();
charWrittenSpy.clear();
// Image B
service->writeCharacteristic(imageIdentityChar, QByteArray::fromHex("1"));
QTest::qWait(1000);
QTRY_COMPARE_WITH_TIMEOUT(charChangedSpy.count(), 1, 5000);
QTRY_COMPARE_WITH_TIMEOUT(charWrittenSpy.count(), 1, 5000);;
entry = charChangedSpy[0];
first = entry[0].value<QLowEnergyCharacteristic>();
val1 = entry[1].toByteArray();
if (val1.size() == 8) {
QCOMPARE(imageIdentityChar, first);
foundOneImage = true;
} else {
// we received a notification for imageBlockChar without explicitly
// enabling them. This is caused by the device's default settings.
QCOMPARE(imageBlockChar, first);
qWarning() << "Invalid image B ident info";
}
entry = charWrittenSpy[0];
second = entry[0].value<QLowEnergyCharacteristic>();
val2 = entry[1].toByteArray();
QCOMPARE(imageIdentityChar, second);
// notifications on non-readable characteristics do not update cache
QVERIFY(imageIdentityChar.value().isEmpty());
QVERIFY(imageBlockChar.value().isEmpty());
/* Bluez resends the last confirmed write value, other platforms
* send the value received by the change notification value.
*/
qDebug() << "Image B(1):" << val1.toHex() << val2.toHex();
QVERIFY(val2 == QByteArray::fromHex("1") || val2 == val1);
QVERIFY2(foundOneImage, "The SensorTag doesn't have a valid image? (1)");
// 5. Trigger image identity announcement (without response)
charChangedSpy.clear();
charWrittenSpy.clear();
foundOneImage = false;
// Image A
service->writeCharacteristic(imageIdentityChar,
QByteArray::fromHex("0"),
QLowEnergyService::WriteWithoutResponse);
// we only expect one signal (the notification but not the write confirmation)
// Wait at least a second for a potential second signals
QTest::qWait(1000);
QTRY_COMPARE_WITH_TIMEOUT(charChangedSpy.count(), 1, 10000);
QTRY_COMPARE_WITH_TIMEOUT(charWrittenSpy.count(), 0, 10000);
entry = charChangedSpy[0];
first = entry[0].value<QLowEnergyCharacteristic>();
val1 = entry[1].toByteArray();
#ifdef Q_OS_ANDROID
QEXPECT_FAIL("", "Android sends write confirmation when using WriteWithoutResponse",
Continue);
#endif
QVERIFY(charWrittenSpy.isEmpty());
if (val1.size() == 8) {
QCOMPARE(first, imageIdentityChar);
foundOneImage = true;
} else {
// we received a notification for imageBlockChar without explicitly
// enabling them. This is caused by the device's default settings.
QCOMPARE(imageBlockChar, first);
qWarning() << "Image A not set?";
}
// notifications on non-readable characteristics do not update cache
QVERIFY(imageIdentityChar.value().isEmpty());
QVERIFY(imageBlockChar.value().isEmpty());
charChangedSpy.clear();
// Image B
service->writeCharacteristic(imageIdentityChar,
QByteArray::fromHex("1"),
QLowEnergyService::WriteWithoutResponse);
// we only expect one signal (the notification but not the write confirmation)
// Wait at least a second for a potential second signals
QTest::qWait(1000);
QTRY_COMPARE_WITH_TIMEOUT(charWrittenSpy.count(), 0, 10000);
QTRY_COMPARE_WITH_TIMEOUT(charChangedSpy.count(), 1, 10000);
entry = charChangedSpy[0];
first = entry[0].value<QLowEnergyCharacteristic>();
val1 = entry[1].toByteArray();
#ifdef Q_OS_ANDROID
QEXPECT_FAIL("", "Android sends write confirmation when using WriteWithoutResponse",
Continue);
#endif
QVERIFY(charWrittenSpy.isEmpty());
if (val1.size() == 8) {
QCOMPARE(first, imageIdentityChar);
foundOneImage = true;
} else {
// we received a notification for imageBlockChar without explicitly
// enabling them. This is caused by the device's default settings.
QCOMPARE(imageBlockChar, first);
qWarning() << "Image B not set?";
}
// notifications on non-readable characteristics do not update cache
QVERIFY(imageIdentityChar.value().isEmpty());
QVERIFY(imageBlockChar.value().isEmpty());
QVERIFY2(foundOneImage, "The SensorTag doesn't have a valid image? (2)");
delete service;
control.disconnectFromDevice();
}
QTEST_MAIN(tst_QLowEnergyController)
#include "tst_qlowenergycontroller.moc"