diff options
author | Andreas Buhr <andreas.buhr@qt.io> | 2021-05-03 13:45:52 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2021-10-26 11:46:04 +0000 |
commit | 435c9c9ab0b708db1b309829f9d586b207fe6ef8 (patch) | |
tree | 2848b781d720c5acb0320605f5a93a55ec5c2b1e | |
parent | ded09dd38793db5cd0c4f42a172c049bbf2eed8a (diff) | |
download | qtconnectivity-435c9c9ab0b708db1b309829f9d586b207fe6ef8.tar.gz |
Add bluetoothtestdevice tool
This patch adds a tool "bluetoothtestdevice" which
acts as a partner device in QtBluetooth unit tests.
Change-Id: I5be89cf555a94a209de093d328fb29f91c2fea9a
Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
(cherry picked from commit f3fcf88a0dc03114fc7bce32b87a866c54eede7d)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r-- | tests/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/bluetoothtestdevice/CMakeLists.txt | 49 | ||||
-rw-r--r-- | tests/bluetoothtestdevice/bluetoothtestdevice.cpp | 368 |
3 files changed, 419 insertions, 0 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 33962f6d..4e7d7c2e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,6 +7,8 @@ if(TARGET Qt::Bluetooth) if(TARGET Qt::Quick) add_subdirectory(bttestui) endif() + + add_subdirectory(bluetoothtestdevice) endif() qt_build_tests() diff --git a/tests/bluetoothtestdevice/CMakeLists.txt b/tests/bluetoothtestdevice/CMakeLists.txt new file mode 100644 index 00000000..ff9edead --- /dev/null +++ b/tests/bluetoothtestdevice/CMakeLists.txt @@ -0,0 +1,49 @@ +##################################################################### +## bluetoothtestdevice Tool: +##################################################################### + +cmake_minimum_required(VERSION 3.16...3.21) + +if(NOT TARGET Qt::Bluetooth) + # for standalone build + project(bluetoothtestdevice LANGUAGES CXX) + + set(CMAKE_AUTOMOC ON) + + find_package(Qt6 COMPONENTS Bluetooth Core) + if(ANDROID OR IOS) + find_package(Qt6 COMPONENTS Gui) + endif() + + qt_add_executable( + bluetoothtestdevice + bluetoothtestdevice.cpp + ) + +else() + + qt_internal_add_executable(bluetoothtestdevice + SOURCES + bluetoothtestdevice.cpp + ) +endif() + +set_target_properties(bluetoothtestdevice PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries( + bluetoothtestdevice + PUBLIC + Qt::Core + Qt::Bluetooth +) + +if(ANDROID OR IOS) + target_link_libraries( + bluetoothtestdevice + PUBLIC + Qt::Gui + ) +endif() diff --git a/tests/bluetoothtestdevice/bluetoothtestdevice.cpp b/tests/bluetoothtestdevice/bluetoothtestdevice.cpp new file mode 100644 index 00000000..ef3a15fc --- /dev/null +++ b/tests/bluetoothtestdevice/bluetoothtestdevice.cpp @@ -0,0 +1,368 @@ +/*************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QBluetoothLocalDevice> +#include <QLatin1String> +#include <QLoggingCategory> +#include <QLowEnergyAdvertisingData> +#include <QLowEnergyAdvertisingParameters> +#include <QLowEnergyCharacteristicData> +#include <QLowEnergyController> +#include <QLowEnergyDescriptorData> +#include <QLowEnergyServiceData> +#include <QLowEnergyDescriptorData> +#include <QTimer> + +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) +#include <QGuiApplication> +#else +#include <QCoreApplication> +#endif // Q_OS_ANDROID || Q_OS_IOS + +#include <thread> + +static const QLatin1String largeCharacteristicServiceUuid("1f85e37c-ac16-11eb-ae5c-93d3a763feed"); +static const QLatin1String largeCharacteristicCharUuid("40e4f68e-ac16-11eb-9956-cfe55a8c370c"); + +static const QLatin1String + notificationIndicationTestServiceUuid("bb137ac5-5716-4b80-873b-e2d11d29efe2"); +static const QLatin1String + notificationIndicationTestChar1Uuid("6da4d652-0248-478a-a5a8-1e2f076158cc"); +static const QLatin1String + notificationIndicationTestChar2Uuid("990930f0-b9cc-4c27-8c1b-ebc2bcae5c95"); +static const QLatin1String + notificationIndicationTestChar3Uuid("9a60486b-de5b-4e03-b914-4e158c0bd388"); +static const QLatin1String + notificationIndicationTestChar4Uuid("d92435d4-6c2e-43f8-a6be-bbb66b5a3e28"); + +static const QLatin1String connectionCountServiceUuid("78c61a07-a0f9-4b92-be2d-2570d8dbf010"); +static const QLatin1String connectionCountCharUuid("9414ec2d-792f-46a2-a19e-186d0fb38a08"); + +static const QLatin1String mtuServiceUuid("9a9483eb-cf4f-4c32-9a6b-794238d5b483"); +static const QLatin1String mtuCharUuid("960d7e2a-a850-4a70-8064-cd74e9ccb6ff"); + +int main(int argc, char *argv[]) +{ + qDebug() << "build:" << __DATE__ << __TIME__; + QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true")); +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + QGuiApplication app(argc, argv); +#else + QCoreApplication app(argc, argv); +#endif // Q_OS_ANDROID || Q_OS_IOS + + // prepare list of services + QList<QLowEnergyServiceData> serviceDefinitions; + + int connectioncount = 0; + { + // connection count service + QLowEnergyServiceData serviceData; + serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary); + serviceData.setUuid(QBluetoothUuid(connectionCountServiceUuid)); + + QLowEnergyCharacteristicData charData; + charData.setUuid(QBluetoothUuid(connectionCountCharUuid)); + size_t size = sizeof(int); + QByteArray initialValue(size, 0); + charData.setValue(initialValue); + charData.setValueLength(size, size); + charData.setProperties(QLowEnergyCharacteristic::PropertyType::Read); + + serviceData.addCharacteristic(charData); + + serviceDefinitions << serviceData; + } + + { + // mtu size service + QLowEnergyServiceData serviceData; + serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary); + serviceData.setUuid(QBluetoothUuid(mtuServiceUuid)); + + QLowEnergyCharacteristicData charData; + charData.setUuid(QBluetoothUuid(mtuCharUuid)); + size_t size = sizeof(int); + int mtu = 23; + QByteArray initialValue((const char *)&mtu, sizeof(int)); + charData.setValue(initialValue); + charData.setValueLength(size, size); + charData.setProperties(QLowEnergyCharacteristic::PropertyType::Read + | QLowEnergyCharacteristic::PropertyType::Notify); + + serviceData.addCharacteristic(charData); + + serviceDefinitions << serviceData; + } + { + // large characteristic service + // + // This is just a service offering a 512 bytes large characteristic which can + // be read and written. It is used to test reading and writing at MTU sizes smaller + // than the size of the characteristic. + QLowEnergyServiceData serviceData; + serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary); + serviceData.setUuid(QBluetoothUuid(largeCharacteristicServiceUuid)); + + QLowEnergyCharacteristicData charData; + charData.setUuid(QBluetoothUuid(largeCharacteristicCharUuid)); + QByteArray initialValue(512, 0); + initialValue[0] = 0x0b; + charData.setValue(initialValue); + charData.setValueLength(512, 512); + charData.setProperties(QLowEnergyCharacteristic::PropertyType::Read + | QLowEnergyCharacteristic::PropertyType::Write); + + serviceData.addCharacteristic(charData); + + serviceDefinitions << serviceData; + } + + { + + // notification service + // + // This is a service which offers: + // - one characteristic which does not support notification or indication + // - one characteristic which does only support notification + // - one characteristic which does only support indication + // - one characteristic which supports both notification or indication + // to test their discovery + QLowEnergyServiceData serviceData; + + serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary); + serviceData.setUuid(QBluetoothUuid(notificationIndicationTestServiceUuid)); + + { + QLowEnergyCharacteristicData charData; + charData.setUuid(QBluetoothUuid(notificationIndicationTestChar1Uuid)); + QByteArray initialValue(8, 0); + initialValue[0] = 0x0b; + charData.setValue(initialValue); + charData.setValueLength(8, 8); + charData.setProperties(QLowEnergyCharacteristic::PropertyType::Read); + + serviceData.addCharacteristic(charData); + } + { + QLowEnergyCharacteristicData charData; + charData.setUuid(QBluetoothUuid(notificationIndicationTestChar2Uuid)); + QByteArray initialValue(8, 0); + initialValue[0] = 0x0b; + charData.setValue(initialValue); + charData.setValueLength(8, 8); + charData.setProperties(QLowEnergyCharacteristic::PropertyType::Read + | QLowEnergyCharacteristic::PropertyType::Notify); + + const QLowEnergyDescriptorData clientConfig( + QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration, + QLowEnergyCharacteristic::CCCDDisable); + charData.addDescriptor(clientConfig); + + serviceData.addCharacteristic(charData); + } + { + QLowEnergyCharacteristicData charData; + charData.setUuid(QBluetoothUuid(notificationIndicationTestChar3Uuid)); + QByteArray initialValue(8, 0); + initialValue[0] = 0x0b; + charData.setValue(initialValue); + charData.setValueLength(8, 8); + charData.setProperties(QLowEnergyCharacteristic::PropertyType::Read + | QLowEnergyCharacteristic::PropertyType::Indicate); + + const QLowEnergyDescriptorData clientConfig( + QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration, + QLowEnergyCharacteristic::CCCDDisable); + charData.addDescriptor(clientConfig); + + serviceData.addCharacteristic(charData); + } + { + QLowEnergyCharacteristicData charData; + charData.setUuid(QBluetoothUuid(notificationIndicationTestChar4Uuid)); + QByteArray initialValue(8, 0); + initialValue[0] = 0x0b; + charData.setValue(initialValue); + charData.setValueLength(8, 8); + charData.setProperties(QLowEnergyCharacteristic::PropertyType::Read + | QLowEnergyCharacteristic::PropertyType::Notify + | QLowEnergyCharacteristic::PropertyType::Indicate); + + const QLowEnergyDescriptorData clientConfig( + QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration, + QLowEnergyCharacteristic::CCCDDisable); + charData.addDescriptor(clientConfig); + + serviceData.addCharacteristic(charData); + } + + serviceDefinitions << serviceData; + } + +#ifndef Q_OS_IOS + auto localAdapters = QBluetoothLocalDevice::allDevices(); + QBluetoothLocalDevice adapter(localAdapters.back().address()); + adapter.setHostMode(QBluetoothLocalDevice::HostDiscoverable); +#endif // Q_OS_IOS + + // Advertising data + QLowEnergyAdvertisingData advertisingData; + advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral); + advertisingData.setIncludePowerLevel(true); + advertisingData.setLocalName("BluetoothTestDevice"); + QList<QBluetoothUuid> serviceUuids; + for (const auto &serviceData : serviceDefinitions) { + serviceUuids << serviceData.uuid(); + } + // leads to too large advertising data + // advertisingData.setServices(serviceUuids); + + // start advertising +#ifndef Q_OS_IOS + auto devices = QBluetoothLocalDevice::allDevices(); + qDebug() << "advertising on" << devices.back().address(); + const QScopedPointer<QLowEnergyController> leController( + QLowEnergyController::createPeripheral(devices.back().address())); +#else + const QScopedPointer<QLowEnergyController> leController( + QLowEnergyController::createPeripheral()); +#endif // Q_OS_IOS + QList<QSharedPointer<QLowEnergyService>> services; + + for (const auto &serviceData : serviceDefinitions) { + services.emplaceBack(leController->addService(serviceData)); + std::this_thread::sleep_for (std::chrono::seconds(1)); + } + + leController->startAdvertising(QLowEnergyAdvertisingParameters(), advertisingData, + advertisingData); + + + auto reconnect = [&connectioncount, &leController, advertisingData, &services, serviceDefinitions]() { + connectioncount++; + for (int i = 0; i < services.size(); ++i) { + services[i].reset(leController->addService(serviceDefinitions[i])); + std::this_thread::sleep_for (std::chrono::seconds(1)); + } + + + { + // set connection counter + Q_ASSERT(services[0]->serviceUuid() + == QBluetoothUuid(connectionCountServiceUuid)); + QByteArray value((const char *)&connectioncount, sizeof(int)); + QLowEnergyCharacteristic characteristic = services[0]->characteristic( + QBluetoothUuid(connectionCountCharUuid)); + Q_ASSERT(characteristic.isValid()); + services[0]->writeCharacteristic(characteristic, value); + } + + bool allValid = true; + for (const auto &service : services) { + allValid = allValid & !service.isNull(); + } + + if (allValid){ + leController->startAdvertising(QLowEnergyAdvertisingParameters(), advertisingData, + advertisingData); + qDebug() << "starting advertising for " << connectioncount << "th time."; + } + else + qDebug() << "Cannot start advertising: Service not valid."; + }; + QObject::connect(leController.data(), &QLowEnergyController::disconnected, reconnect); + + QObject::connect(leController.data(), &QLowEnergyController::mtuChanged, [&services](int mtu) { + qDebug() << "MTU changed, callback called."; + Q_ASSERT(services[1]->serviceUuid() == QBluetoothUuid(mtuServiceUuid)); + QByteArray value((const char *)&mtu, sizeof(int)); + QLowEnergyCharacteristic characteristic = + services[1]->characteristic(QBluetoothUuid(mtuCharUuid)); + Q_ASSERT(characteristic.isValid()); + services[1]->writeCharacteristic(characteristic, value); + }); + + QTimer notificationTestTimer; + quint64 currentCharacteristicValue = 0; + const auto characteristicChanger = [&services, ¤tCharacteristicValue]() { + QByteArray value((const char *)¤tCharacteristicValue, 8); + + Q_ASSERT(services[3]->serviceUuid() + == QBluetoothUuid(notificationIndicationTestServiceUuid)); + { + QLowEnergyCharacteristic characteristic = services[3]->characteristic( + QBluetoothUuid(notificationIndicationTestChar2Uuid)); + Q_ASSERT(characteristic.isValid()); + services[3]->writeCharacteristic(characteristic, + value); // Potentially causes notification. + } + { + QLowEnergyCharacteristic characteristic = services[3]->characteristic( + QBluetoothUuid(notificationIndicationTestChar3Uuid)); + Q_ASSERT(characteristic.isValid()); + services[3]->writeCharacteristic(characteristic, + value); // Potentially causes notification. + } + { + QLowEnergyCharacteristic characteristic = services[3]->characteristic( + QBluetoothUuid(notificationIndicationTestChar4Uuid)); + Q_ASSERT(characteristic.isValid()); + services[3]->writeCharacteristic(characteristic, + value); // Potentially causes notification. + } + + ++currentCharacteristicValue; + }; + QObject::connect(¬ificationTestTimer, &QTimer::timeout, characteristicChanger); + notificationTestTimer.start(100); + + return app.exec(); +} |