diff options
Diffstat (limited to 'tests')
8 files changed, 770 insertions, 0 deletions
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 9bbf792d..f1112240 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -20,6 +20,7 @@ qtHaveModule(bluetooth) { qlowenergycharacteristic \ qlowenergydescriptor \ qlowenergycontroller \ + qlowenergycontroller-gattserver \ qlowenergyservice } diff --git a/tests/auto/qlowenergycontroller-gattserver/README b/tests/auto/qlowenergycontroller-gattserver/README new file mode 100644 index 00000000..bd63ef85 --- /dev/null +++ b/tests/auto/qlowenergycontroller-gattserver/README @@ -0,0 +1,10 @@ +This test is split into a server and a client part. The former is supplying data, and +the latter is implementing the actual test application. +To run a full test, follow these steps: + 1) Start the server application on some machine that has a Bluetooth LE adapter + and is close enough to the client machine. + 2) On the client machine, set the QT_BT_GATTSERVER_TEST_ADDRESS environment variable + to the address of the Bluetooth adapter on the server machine. + 3) Run the test on the client. +If you skip steps 1) or 2), only a few unit tests will be run. These do not require the +test machine to have a Bluetooth adapter. diff --git a/tests/auto/qlowenergycontroller-gattserver/qlowenergycontroller-gattserver.pro b/tests/auto/qlowenergycontroller-gattserver/qlowenergycontroller-gattserver.pro new file mode 100644 index 00000000..8b6c52e7 --- /dev/null +++ b/tests/auto/qlowenergycontroller-gattserver/qlowenergycontroller-gattserver.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS = server test diff --git a/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp new file mode 100644 index 00000000..087b0284 --- /dev/null +++ b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp @@ -0,0 +1,226 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtBluetooth/qlowenergyadvertisingdata.h> +#include <QtBluetooth/qlowenergyadvertisingparameters.h> +#include <QtBluetooth/qlowenergyconnectionparameters.h> +#include <QtBluetooth/qlowenergycontroller.h> +#include <QtBluetooth/qlowenergycharacteristicdata.h> +#include <QtBluetooth/qlowenergydescriptordata.h> +#include <QtBluetooth/qlowenergyservicedata.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qendian.h> +#include <QtCore/qhash.h> +#include <QtCore/qscopedpointer.h> +#include <QtCore/qsharedpointer.h> +#include <QtCore/qvector.h> + +static QByteArray deviceName() { return "Qt GATT server"; } + +static QScopedPointer<QLowEnergyController> leController; +typedef QSharedPointer<QLowEnergyService> ServicePtr; +static QHash<QBluetoothUuid, ServicePtr> services; +static int descriptorWriteCount = 0; +static int disconnectCount = 0; +static QBluetoothAddress remoteDevice; + +void addService(const QLowEnergyServiceData &serviceData) +{ + const ServicePtr service(leController->addService(serviceData)); + Q_ASSERT(service); + services.insert(service->serviceUuid(), service); +} + +void addRunningSpeedService() +{ + QLowEnergyServiceData serviceData; + serviceData.setUuid(QBluetoothUuid::RunningSpeedAndCadence); + serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary); + + QLowEnergyDescriptorData desc; + desc.setUuid(QBluetoothUuid::ClientCharacteristicConfiguration); + desc.setValue(QByteArray(2, 0)); // Default: No indication, no notification. + QLowEnergyCharacteristicData charData; + charData.setUuid(QBluetoothUuid::RSCMeasurement); + charData.addDescriptor(desc); + charData.setProperties(QLowEnergyCharacteristic::Notify); + QByteArray value(4, 0); + value[0] = 1 << 2; // "Running", no optional fields. + charData.setValue(value); + serviceData.addCharacteristic(charData); + + charData = QLowEnergyCharacteristicData(); + charData.setUuid(QBluetoothUuid::RSCFeature); + charData.setProperties(QLowEnergyCharacteristic::Read); + value = QByteArray(2, 0); + qToLittleEndian<quint16>(1 << 2, reinterpret_cast<uchar *>(value.data())); + charData.setValue(value); + serviceData.addCharacteristic(charData); + addService(serviceData); +} + +void addGenericAccessService() +{ + QLowEnergyServiceData serviceData; + serviceData.setUuid(QBluetoothUuid::GenericAccess); + serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary); + + QLowEnergyCharacteristicData charData; + charData.setUuid(QBluetoothUuid::DeviceName); + charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Write); + charData.setValue(deviceName()); + serviceData.addCharacteristic(charData); + + charData = QLowEnergyCharacteristicData(); + charData.setUuid(QBluetoothUuid::Appearance); + charData.setProperties(QLowEnergyCharacteristic::Read); + QByteArray value(2, 0); + qToLittleEndian<quint16>(128, reinterpret_cast<uchar *>(value.data())); // Generic computer. + charData.setValue(value); + serviceData.addCharacteristic(charData); + + serviceData.addIncludedService(services.value(QBluetoothUuid::RunningSpeedAndCadence).data()); + addService(serviceData); +} + +void addCustomService() +{ + QLowEnergyServiceData serviceData; + serviceData.setUuid(QBluetoothUuid(quint16(0x2000))); + serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary); + + QLowEnergyCharacteristicData charData; + charData.setUuid(QBluetoothUuid(quint16(0x5000))); // Made up. + charData.setProperties(QLowEnergyCharacteristic::Read); + charData.setValue(QByteArray(1024, 'x')); // Long value to test "Read Blob". + serviceData.addCharacteristic(charData); + + charData.setUuid(QBluetoothUuid(quint16(0x5001))); + charData.setProperties(QLowEnergyCharacteristic::Read); + charData.setReadConstraints(QBluetooth::AttAuthorizationRequired); // To test read failure. + serviceData.addCharacteristic(charData); + charData.setValue("something"); + + charData.setUuid(QBluetoothUuid(quint16(0x5002))); + charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Indicate); + charData.setReadConstraints(QBluetooth::AttAccessConstraints()); + const QLowEnergyDescriptorData desc(QBluetoothUuid::ClientCharacteristicConfiguration, + QByteArray(2, 0)); + charData.addDescriptor(desc); + serviceData.addCharacteristic(charData); + + charData.setUuid(QBluetoothUuid(quint16(0x5003))); + charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify); + serviceData.addCharacteristic(charData); + + addService(serviceData); +} + +void startAdvertising() +{ + QLowEnergyAdvertisingParameters params; + params.setMode(QLowEnergyAdvertisingParameters::AdvInd); + QLowEnergyAdvertisingData data; + data.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityLimited); + data.setServices(services.keys()); + data.setIncludePowerLevel(true); + data.setLocalName(deviceName()); + leController->startAdvertising(params, data); +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + leController.reset(QLowEnergyController::createPeripheral()); + addRunningSpeedService(); + addGenericAccessService(); + addCustomService(); + startAdvertising(); + + const ServicePtr customService = services.value(QBluetoothUuid(quint16(0x2000))); + Q_ASSERT(customService); + + const auto stateChangedHandler = [customService]() { + switch (leController->state()) { + case QLowEnergyController::ConnectedState: + remoteDevice = leController->remoteAddress(); + break; + case QLowEnergyController::UnconnectedState: { + if (++disconnectCount == 2) { + qApp->quit(); + break; + } + Q_ASSERT(disconnectCount == 1); + const QLowEnergyCharacteristic indicatableChar + = customService->characteristic(QBluetoothUuid(quint16(0x5002))); + Q_ASSERT(indicatableChar.isValid()); + customService->writeCharacteristic(indicatableChar, "indicated2"); + Q_ASSERT(indicatableChar.value() == "indicated2"); + const QLowEnergyCharacteristic notifiableChar + = customService->characteristic(QBluetoothUuid(quint16(0x5003))); + Q_ASSERT(notifiableChar.isValid()); + customService->writeCharacteristic(notifiableChar, "notified2"); + Q_ASSERT(notifiableChar.value() == "notified2"); + startAdvertising(); + break; + } + default: + break; + } + }; + + QObject::connect(leController.data(), &QLowEnergyController::stateChanged, stateChangedHandler); + const auto descriptorWriteHandler = [customService]() { + if (++descriptorWriteCount != 2) + return; + const QLowEnergyCharacteristic indicatableChar + = customService->characteristic(QBluetoothUuid(quint16(0x5002))); + Q_ASSERT(indicatableChar.isValid()); + customService->writeCharacteristic(indicatableChar, "indicated"); + Q_ASSERT(indicatableChar.value() == "indicated"); + const QLowEnergyCharacteristic notifiableChar + = customService->characteristic(QBluetoothUuid(quint16(0x5003))); + Q_ASSERT(notifiableChar.isValid()); + customService->writeCharacteristic(notifiableChar, "notified"); + Q_ASSERT(notifiableChar.value() == "notified"); + QLowEnergyConnectionParameters connParams; + connParams.setIntervalRange(30, 62.5); + connParams.setLatency(5); + connParams.setSupervisionTimeout(5500); + leController->requestConnectionUpdate(connParams); + }; + QObject::connect(customService.data(), &QLowEnergyService::descriptorWritten, + descriptorWriteHandler); + + return app.exec(); +} diff --git a/tests/auto/qlowenergycontroller-gattserver/server/server.pro b/tests/auto/qlowenergycontroller-gattserver/server/server.pro new file mode 100644 index 00000000..b9f2ccf9 --- /dev/null +++ b/tests/auto/qlowenergycontroller-gattserver/server/server.pro @@ -0,0 +1,5 @@ +QT = core bluetooth + +CONFIG += c++11 + +SOURCES = qlowenergycontroller-gattserver.cpp diff --git a/tests/auto/qlowenergycontroller-gattserver/test/test.pro b/tests/auto/qlowenergycontroller-gattserver/test/test.pro new file mode 100644 index 00000000..bd1f0874 --- /dev/null +++ b/tests/auto/qlowenergycontroller-gattserver/test/test.pro @@ -0,0 +1,6 @@ +QT = core bluetooth testlib + +TARGET = tst_qlowenergycontroller-gattserver +CONFIG += testcase c++11 + +SOURCES += tst_qlowenergycontroller-gattserver.cpp diff --git a/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp new file mode 100644 index 00000000..e2534a14 --- /dev/null +++ b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp @@ -0,0 +1,519 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtBluetooth/qbluetoothaddress.h> +#include <QtBluetooth/qbluetoothdevicediscoveryagent.h> +#include <QtBluetooth/qbluetoothdeviceinfo.h> +#include <QtBluetooth/qbluetoothlocaldevice.h> +#include <QtBluetooth/qlowenergyadvertisingdata.h> +#include <QtBluetooth/qlowenergyadvertisingparameters.h> +#include <QtBluetooth/qlowenergyconnectionparameters.h> +#include <QtBluetooth/qlowenergycontroller.h> +#include <QtBluetooth/qlowenergycharacteristicdata.h> +#include <QtBluetooth/qlowenergydescriptordata.h> +#include <QtBluetooth/qlowenergyservicedata.h> +#include <QtCore/qendian.h> +#include <QtCore/qscopedpointer.h> +#include <QtTest/qsignalspy.h> +#include <QtTest/QtTest> + +#include <algorithm> + +using namespace QBluetooth; + +class TestQLowEnergyControllerGattServer : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + + // Static, local stuff goes here. + void advertisingParameters(); + void advertisingData(); + void connectionParameters(); + void controllerType(); + void serviceData(); + + // Interaction with actual GATT server goes here. Order is relevant. + void advertisedData(); + void serverCommunication(); + +private: + QBluetoothAddress m_serverAddress; + QBluetoothDeviceInfo m_serverInfo; + QScopedPointer<QLowEnergyController> m_leController; +}; + + +void TestQLowEnergyControllerGattServer::initTestCase() +{ + const QString serverAddress = qgetenv("QT_BT_GATTSERVER_TEST_ADDRESS"); + if (serverAddress.isEmpty()) + return; + m_serverAddress = QBluetoothAddress(serverAddress); + QVERIFY(!m_serverAddress.isNull()); +} + +void TestQLowEnergyControllerGattServer::advertisingParameters() +{ + QLowEnergyAdvertisingParameters params; + QCOMPARE(params, QLowEnergyAdvertisingParameters()); + QCOMPARE(params.filterPolicy(), QLowEnergyAdvertisingParameters::IgnoreWhiteList); + QCOMPARE(params.minimumInterval(), 1280); + QCOMPARE(params.maximumInterval(), 1280); + QCOMPARE(params.mode(), QLowEnergyAdvertisingParameters::AdvInd); + QVERIFY(params.whiteList().isEmpty()); + + params.setInterval(100, 200); + QCOMPARE(params.minimumInterval(), 100); + QCOMPARE(params.maximumInterval(), 200); + params.setInterval(200, 100); + QCOMPARE(params.minimumInterval(), 200); + QCOMPARE(params.maximumInterval(), 200); + + params.setMode(QLowEnergyAdvertisingParameters::AdvScanInd); + QCOMPARE(params.mode(), QLowEnergyAdvertisingParameters::AdvScanInd); + + const auto whiteList = QList<QLowEnergyAdvertisingParameters::AddressInfo>() + << QLowEnergyAdvertisingParameters::AddressInfo(QBluetoothAddress(), + QLowEnergyController::PublicAddress); + params.setWhiteList(whiteList, QLowEnergyAdvertisingParameters::UseWhiteListForConnecting); + QCOMPARE(params.whiteList(), whiteList); + QCOMPARE(params.filterPolicy(), QLowEnergyAdvertisingParameters::UseWhiteListForConnecting); + QVERIFY(params != QLowEnergyAdvertisingParameters()); +} + +void TestQLowEnergyControllerGattServer::advertisingData() +{ + QLowEnergyAdvertisingData data; + QCOMPARE(data, QLowEnergyAdvertisingData()); + QCOMPARE(data.discoverability(), QLowEnergyAdvertisingData::DiscoverabilityNone); + QCOMPARE(data.includePowerLevel(), false); + QCOMPARE(data.localName(), QString()); + QCOMPARE(data.manufacturerData(), QByteArray()); + QCOMPARE(data.manufacturerId(), QLowEnergyAdvertisingData::invalidManufacturerId()); + QVERIFY(data.services().isEmpty()); + + data.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityLimited); + QCOMPARE(data.discoverability(), QLowEnergyAdvertisingData::DiscoverabilityLimited); + + data.setIncludePowerLevel(true); + QCOMPARE(data.includePowerLevel(), true); + + data.setLocalName("A device name"); + QCOMPARE(data.localName(), QString("A device name")); + + data.setManufacturerData(0xfffd, "some data"); + QCOMPARE(data.manufacturerId(), quint16(0xfffd)); + QCOMPARE(data.manufacturerData(), QByteArray("some data")); + + const auto services = QList<QBluetoothUuid>() << QBluetoothUuid::CurrentTimeService + << QBluetoothUuid::DeviceInformation; + data.setServices(services); + QCOMPARE(data.services(), services); + + QByteArray rawData(7, 'x'); + data.setRawData(rawData); + QCOMPARE(data.rawData(), rawData); + + QVERIFY(data != QLowEnergyAdvertisingData()); +} + +void TestQLowEnergyControllerGattServer::connectionParameters() +{ + QLowEnergyConnectionParameters connParams; + QCOMPARE(connParams, QLowEnergyConnectionParameters()); + connParams.setIntervalRange(8, 9); + QCOMPARE(connParams.minimumInterval(), double(8)); + QCOMPARE(connParams.maximumInterval(), double(9)); + connParams.setIntervalRange(9, 8); + QCOMPARE(connParams.minimumInterval(), double(9)); + QCOMPARE(connParams.maximumInterval(), double(9)); + connParams.setLatency(50); + QCOMPARE(connParams.latency(), 50); + connParams.setSupervisionTimeout(1000); + QCOMPARE(connParams.supervisionTimeout(), 1000); + const QLowEnergyConnectionParameters cp2 = connParams; + QCOMPARE(cp2, connParams); + QLowEnergyConnectionParameters cp3; + QVERIFY(cp3 != connParams); + cp3 = connParams; + QCOMPARE(cp3, connParams); +} + +void TestQLowEnergyControllerGattServer::advertisedData() +{ + if (m_serverAddress.isNull()) + QSKIP("No server address provided"); + QBluetoothDeviceDiscoveryAgent discoveryAgent; + discoveryAgent.start(); + QSignalSpy spy(&discoveryAgent, SIGNAL(finished())); + QVERIFY(spy.wait(30000)); + const QList<QBluetoothDeviceInfo> devices = discoveryAgent.discoveredDevices(); + const auto it = std::find_if(devices.constBegin(), devices.constEnd(), + [this](const QBluetoothDeviceInfo &device) { return device.address() == m_serverAddress; }); + QVERIFY(it != devices.constEnd()); + m_serverInfo = *it; + + // BlueZ seems to interfere with the advertising in some way, so that in addition to the name + // we set, the host name of the machine is also sent. Therefore we cannot guarantee that "our" + // name is seen on the scanning machine. + // QCOMPARE(m_serverInfo.name(), QString("Qt GATT server")); + + QCOMPARE(m_serverInfo.serviceUuids().count(), 3); + QVERIFY(m_serverInfo.serviceUuids().contains(QBluetoothUuid::GenericAccess)); + QVERIFY(m_serverInfo.serviceUuids().contains(QBluetoothUuid::RunningSpeedAndCadence)); + QVERIFY(m_serverInfo.serviceUuids().contains(QBluetoothUuid(quint16(0x2000)))); +} + +// TODO: Why on earth is this not in the library??? +Q_DECLARE_METATYPE(QLowEnergyCharacteristic) +Q_DECLARE_METATYPE(QLowEnergyDescriptor) + +void TestQLowEnergyControllerGattServer::serverCommunication() +{ + qRegisterMetaType<QLowEnergyCharacteristic>(); + qRegisterMetaType<QLowEnergyDescriptor>(); + + if (m_serverAddress.isNull()) + QSKIP("No server address provided"); + m_leController.reset(QLowEnergyController::createCentral(m_serverInfo)); + QVERIFY(!m_leController.isNull()); + m_leController->connectToDevice(); + QScopedPointer<QSignalSpy> spy(new QSignalSpy(m_leController.data(), + &QLowEnergyController::connected)); + QVERIFY(spy->wait(30000)); + m_leController->discoverServices(); + spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::discoveryFinished)); + QVERIFY(spy->wait(30000)); + const QList<QBluetoothUuid> serviceUuids = m_leController->services(); + QCOMPARE(serviceUuids.count(), 3); + QVERIFY(serviceUuids.contains(QBluetoothUuid::GenericAccess)); + QVERIFY(serviceUuids.contains(QBluetoothUuid::RunningSpeedAndCadence)); + QVERIFY(serviceUuids.contains(QBluetoothUuid(quint16(0x2000)))); + + const QScopedPointer<QLowEnergyService> genericAccessService( + m_leController->createServiceObject(QBluetoothUuid::GenericAccess)); + QVERIFY(!genericAccessService.isNull()); + genericAccessService->discoverDetails(); + while (genericAccessService->state() != QLowEnergyService::ServiceDiscovered) { + spy.reset(new QSignalSpy(genericAccessService.data(), &QLowEnergyService::stateChanged)); + QVERIFY(spy->wait(3000)); + } + QCOMPARE(genericAccessService->includedServices().count(), 1); + QCOMPARE(genericAccessService->includedServices().first(), + QBluetoothUuid(QBluetoothUuid::RunningSpeedAndCadence)); + QCOMPARE(genericAccessService->characteristics().count(), 2); + const QLowEnergyCharacteristic deviceNameChar + = genericAccessService->characteristic(QBluetoothUuid::DeviceName); + QVERIFY(deviceNameChar.isValid()); + QCOMPARE(deviceNameChar.descriptors().count(), 0); + QCOMPARE(deviceNameChar.properties(), + QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Write); + QCOMPARE(deviceNameChar.value().constData(), "Qt GATT server"); + const QLowEnergyCharacteristic appearanceChar + = genericAccessService->characteristic(QBluetoothUuid::Appearance); + QVERIFY(appearanceChar.isValid()); + QCOMPARE(appearanceChar.descriptors().count(), 0); + QCOMPARE(appearanceChar.properties(), QLowEnergyCharacteristic::Read); + auto value = qFromLittleEndian<quint16>(reinterpret_cast<const uchar *>( + appearanceChar.value().constData())); + QCOMPARE(value, quint16(128)); + + const QScopedPointer<QLowEnergyService> runningSpeedService( + m_leController->createServiceObject(QBluetoothUuid::RunningSpeedAndCadence)); + QVERIFY(!runningSpeedService.isNull()); + runningSpeedService->discoverDetails(); + while (runningSpeedService->state() != QLowEnergyService::ServiceDiscovered) { + spy.reset(new QSignalSpy(runningSpeedService.data(), &QLowEnergyService::stateChanged)); + QVERIFY(spy->wait(3000)); + } + QCOMPARE(runningSpeedService->includedServices().count(), 0); + QCOMPARE(runningSpeedService->characteristics().count(), 2); + QLowEnergyCharacteristic measurementChar + = runningSpeedService->characteristic(QBluetoothUuid::RSCMeasurement); + QVERIFY(measurementChar.isValid()); + QCOMPARE(measurementChar.descriptors().count(), 1); + const QLowEnergyDescriptor clientConfigDesc + = measurementChar.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + QVERIFY(clientConfigDesc.isValid()); + QCOMPARE(clientConfigDesc.value(), QByteArray(2, 0)); + QCOMPARE(measurementChar.properties(), QLowEnergyCharacteristic::Notify); + QCOMPARE(measurementChar.value(), QByteArray()); // Empty because Read property not set + QLowEnergyCharacteristic featureChar + = runningSpeedService->characteristic(QBluetoothUuid::RSCFeature); + QVERIFY(featureChar.isValid()); + QCOMPARE(featureChar.descriptors().count(), 0); + QCOMPARE(featureChar.properties(), QLowEnergyCharacteristic::Read); + value = qFromLittleEndian<quint16>(reinterpret_cast<const uchar *>( + featureChar.value().constData())); + QCOMPARE(value, quint16(1 << 2)); + + QScopedPointer<QLowEnergyService> customService( + m_leController->createServiceObject(QBluetoothUuid(quint16(0x2000)))); + QVERIFY(!customService.isNull()); + customService->discoverDetails(); + while (customService->state() != QLowEnergyService::ServiceDiscovered) { + spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::stateChanged)); + QVERIFY(spy->wait(5000)); + } + QCOMPARE(customService->includedServices().count(), 0); + QCOMPARE(customService->characteristics().count(), 4); + QLowEnergyCharacteristic customChar + = customService->characteristic(QBluetoothUuid(quint16(0x5000))); + QVERIFY(customChar.isValid()); + QCOMPARE(customChar.descriptors().count(), 0); + QCOMPARE(customChar.value(), QByteArray(1024, 'x')); + + QLowEnergyCharacteristic customChar2 + = customService->characteristic(QBluetoothUuid(quint16(0x5001))); + QVERIFY(customChar2.isValid()); + QCOMPARE(customChar2.descriptors().count(), 0); + QCOMPARE(customChar2.value(), QByteArray()); // Was not readable due to authorization requirement. + + QLowEnergyCharacteristic customChar3 + = customService->characteristic(QBluetoothUuid(quint16(0x5002))); + QVERIFY(customChar3.isValid()); + QCOMPARE(customChar3.properties(), + QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Indicate); + QCOMPARE(customChar3.descriptors().count(), 1); + QLowEnergyDescriptor cc3ClientConfig + = customChar3.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + QVERIFY(cc3ClientConfig.isValid()); + + QLowEnergyCharacteristic customChar4 + = customService->characteristic(QBluetoothUuid(quint16(0x5003))); + QVERIFY(customChar4.isValid()); + QCOMPARE(customChar4.properties(), + QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify); + QCOMPARE(customChar4.descriptors().count(), 1); + QLowEnergyDescriptor cc4ClientConfig + = customChar4.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + QVERIFY(cc4ClientConfig.isValid()); + + customService->writeCharacteristic(customChar, "whatever"); + spy.reset(new QSignalSpy(customService.data(), static_cast<void (QLowEnergyService::*) + (QLowEnergyService::ServiceError)>(&QLowEnergyService::error))); + QVERIFY(spy->wait(3000)); + QCOMPARE(customService->error(), QLowEnergyService::CharacteristicWriteError); + + QByteArray indicateValue(2, 0); + qToLittleEndian<quint16>(2, reinterpret_cast<uchar *>(indicateValue.data())); + customService->writeDescriptor(cc3ClientConfig, indicateValue); + spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::descriptorWritten)); + QVERIFY(spy->wait(3000)); + + QByteArray notifyValue(2, 0); + qToLittleEndian<quint16>(1, reinterpret_cast<uchar *>(notifyValue.data())); + customService->writeDescriptor(cc4ClientConfig, notifyValue); + spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::descriptorWritten)); + QVERIFY(spy->wait(3000)); + + // Server now changes the characteristic values. + + spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::characteristicChanged)); + QVERIFY(spy->wait(3000)); + if (spy->count() == 1) + QVERIFY(spy->wait(3000)); + QCOMPARE(customChar3.value().constData(), "indicated"); + QCOMPARE(customChar4.value().constData(), "notified"); + + spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::connectionUpdated)); + QVERIFY(spy->wait(5000)); + + const bool isBonded = QBluetoothLocalDevice().pairingStatus(m_serverAddress) + != QBluetoothLocalDevice::Unpaired; + m_leController->disconnectFromDevice(); + + if (m_leController->state() != QLowEnergyController::UnconnectedState) { + spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::stateChanged)); + QVERIFY(spy->wait(3000)); + } + QCOMPARE(m_leController->state(), QLowEnergyController::UnconnectedState); + + // Server now changes the characteristic values again while we're offline. + // Note: We cannot test indications and notifications for this case, as the client does + // not cache the old information and thus does not yet know the characteristics + // at the time the notification/indication is received. + + QTest::qWait(3000); + m_leController->connectToDevice(); + spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::connected)); + QVERIFY(spy->wait(30000)); + m_leController->discoverServices(); + spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::discoveryFinished)); + QVERIFY(spy->wait(30000)); + customService.reset(m_leController->createServiceObject(QBluetoothUuid(quint16(0x2000)))); + QVERIFY(!customService.isNull()); + customService->discoverDetails(); + while (customService->state() != QLowEnergyService::ServiceDiscovered) { + spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::stateChanged)); + QVERIFY(spy->wait(5000)); + } + customChar3 = customService->characteristic(QBluetoothUuid(quint16(0x5002))); + QVERIFY(customChar3.isValid()); + QCOMPARE(customChar3.value().constData(), "indicated2"); + customChar4 = customService->characteristic(QBluetoothUuid(quint16(0x5003))); + QVERIFY(customChar4.isValid()); + QCOMPARE(customChar4.value().constData(), "notified2"); + cc3ClientConfig = customChar3.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + QVERIFY(cc3ClientConfig.isValid()); + cc4ClientConfig = customChar4.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + QVERIFY(cc4ClientConfig.isValid()); + if (isBonded) { + QCOMPARE(cc3ClientConfig.value(), indicateValue); + QCOMPARE(cc4ClientConfig.value(), notifyValue); + } else { + QCOMPARE(cc3ClientConfig.value(), QByteArray(2, 0)); + QCOMPARE(cc4ClientConfig.value(), QByteArray(2, 0)); + } +} + +void TestQLowEnergyControllerGattServer::controllerType() +{ + const QScopedPointer<QLowEnergyController> controller(QLowEnergyController::createPeripheral()); + QVERIFY(!controller.isNull()); + QCOMPARE(controller->role(), QLowEnergyController::PeripheralRole); +} + +void TestQLowEnergyControllerGattServer::serviceData() +{ + QLowEnergyDescriptorData descData; + QVERIFY(!descData.isValid()); + + descData.setUuid(QBluetoothUuid::ValidRange); + QCOMPARE(descData.uuid(), QBluetoothUuid(QBluetoothUuid::ValidRange)); + QVERIFY(descData.isValid()); + QVERIFY(descData != QLowEnergyDescriptorData()); + + descData.setValue("xyz"); + QCOMPARE(descData.value().constData(), "xyz"); + + descData.setReadPermissions(true, AttAuthenticationRequired); + QCOMPARE(descData.isReadable(), true); + QCOMPARE(descData.readConstraints(), AttAuthenticationRequired); + + descData.setWritePermissions(false); + QCOMPARE(descData.isWritable(), false); + + QLowEnergyDescriptorData descData2(QBluetoothUuid::ReportReference, "abc"); + QVERIFY(descData2 != QLowEnergyDescriptorData()); + QVERIFY(descData2.isValid()); + QCOMPARE(descData2.uuid(), QBluetoothUuid(QBluetoothUuid::ReportReference)); + QCOMPARE(descData2.value().constData(), "abc"); + + QLowEnergyCharacteristicData charData; + QVERIFY(!charData.isValid()); + + charData.setUuid(QBluetoothUuid::BatteryLevel); + QVERIFY(charData != QLowEnergyCharacteristicData()); + QCOMPARE(charData.uuid(), QBluetoothUuid(QBluetoothUuid::BatteryLevel)); + QVERIFY(charData.isValid()); + + charData.setValue("value"); + QCOMPARE(charData.value().constData(), "value"); + + charData.setValueLength(4, 7); + QCOMPARE(charData.minimumValueLength(), 4); + QCOMPARE(charData.maximumValueLength(), 7); + charData.setValueLength(5, 2); + QCOMPARE(charData.minimumValueLength(), 5); + QCOMPARE(charData.maximumValueLength(), 5); + + const QLowEnergyCharacteristic::PropertyTypes props + = QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::WriteSigned; + charData.setProperties(props); + QCOMPARE(charData.properties(), props); + + charData.setReadConstraints(AttEncryptionRequired); + QCOMPARE(charData.readConstraints(), AttEncryptionRequired); + charData.setWriteConstraints(AttAuthenticationRequired | AttAuthorizationRequired); + QCOMPARE(charData.writeConstraints(), AttAuthenticationRequired | AttAuthorizationRequired); + + charData.setDescriptors(QList<QLowEnergyDescriptorData>() << descData << descData2); + QLowEnergyDescriptorData descData3(QBluetoothUuid::ExternalReportReference, "someval"); + charData.addDescriptor(descData3); + charData.addDescriptor(QLowEnergyDescriptorData()); // Invalid. + QCOMPARE(charData.descriptors(), + QList<QLowEnergyDescriptorData>() << descData << descData2 << descData3); + + QLowEnergyServiceData secondaryData; + QVERIFY(!secondaryData.isValid()); + + secondaryData.setUuid(QBluetoothUuid::SerialPort); + QCOMPARE(secondaryData.uuid(), QBluetoothUuid(QBluetoothUuid::SerialPort)); + QVERIFY(secondaryData.isValid()); + QVERIFY(secondaryData != QLowEnergyServiceData()); + + secondaryData.setType(QLowEnergyServiceData::ServiceTypeSecondary); + QCOMPARE(secondaryData.type(), QLowEnergyServiceData::ServiceTypeSecondary); + + secondaryData.setCharacteristics(QList<QLowEnergyCharacteristicData>() + << charData << QLowEnergyCharacteristicData()); + QCOMPARE(secondaryData.characteristics(), QList<QLowEnergyCharacteristicData>() << charData); + +#ifdef Q_OS_DARWIN + QSKIP("GATT server functionality not implemented for Apple platforms"); +#endif + const QScopedPointer<QLowEnergyController> controller(QLowEnergyController::createPeripheral()); + QVERIFY(!controller->addService(QLowEnergyServiceData())); + const QScopedPointer<QLowEnergyService> secondaryService(controller->addService(secondaryData)); + QVERIFY(!secondaryService.isNull()); + QCOMPARE(secondaryService->serviceUuid(), secondaryData.uuid()); + const QList<QLowEnergyCharacteristic> characteristics = secondaryService->characteristics(); + QCOMPARE(characteristics.count(), 1); + QCOMPARE(characteristics.first().uuid(), charData.uuid()); + const QList<QLowEnergyDescriptor> descriptors = characteristics.first().descriptors(); + QCOMPARE(descriptors.count(), 3); + const auto inUuids = QSet<QBluetoothUuid>() << descData.uuid() << descData2.uuid() + << descData3.uuid(); + QSet<QBluetoothUuid> outUuids; + foreach (const QLowEnergyDescriptor &desc, descriptors) + outUuids << desc.uuid(); + QCOMPARE(inUuids, outUuids); + + QLowEnergyServiceData primaryData; + primaryData.setUuid(QBluetoothUuid::Headset); + primaryData.addIncludedService(secondaryService.data()); + const QScopedPointer<QLowEnergyService> primaryService(controller->addService(primaryData)); + QVERIFY(!primaryService.isNull()); + QCOMPARE(primaryService->characteristics().count(), 0); + const QList<QBluetoothUuid> includedServices = primaryService->includedServices(); + QCOMPARE(includedServices.count(), 1); + QCOMPARE(includedServices.first(), secondaryService->serviceUuid()); +} + +QTEST_MAIN(TestQLowEnergyControllerGattServer) + +#include "tst_qlowenergycontroller-gattserver.moc" diff --git a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp index e85ea44a..a8358dea 100644 --- a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp +++ b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp @@ -252,6 +252,7 @@ void tst_QLowEnergyController::tst_connect() QSKIP("No local Bluetooth or remote BTLE device found. Skipping test."); QLowEnergyController control(remoteDeviceInfo); + QCOMPARE(control.role(), QLowEnergyController::CentralRole); QSignalSpy connectedSpy(&control, SIGNAL(connected())); QSignalSpy disconnectedSpy(&control, SIGNAL(disconnected())); if (remoteDeviceInfo.name().isEmpty()) |