diff options
author | Juha Vuolle <juha.vuolle@insta.fi> | 2022-05-31 13:17:33 +0300 |
---|---|---|
committer | Juha Vuolle <juha.vuolle@insta.fi> | 2022-06-09 16:41:52 +0300 |
commit | a7b6e87a559e2cdf783b1bd65bbad2adbe372d79 (patch) | |
tree | 5ff37e12bc435663ff47dab97dd849b90dfdc1fb /tests | |
parent | eaa0f9dbd55e4d9241f89ce402eb990d3998153d (diff) | |
download | qtconnectivity-a7b6e87a559e2cdf783b1bd65bbad2adbe372d79.tar.gz |
Add Bluetooth LE functionality to bttestui
Bttestui is a manual bluetooth test application with which one can
manually test different bluetooth features. This commit adds Low Energy
support for the application.
The support is logically simple and flat meaning that it does not
on purpose implement automated state flows. Rather one can manually
trigger each action and modify them locally as needed for testing
purposes.
The commit adds base support for both central and peripheral roles and
their basic functionalities: discovery, advertisement, read and write.
In addition few changes:
- Make the main.qml flickable to be able to use on small screens
- Remove QML import numbering
- Add nullptr checks in some of the functions, sometimes it is
intentional to eg. disable localDevice creation
Fixes: QTBUG-103827
Change-Id: I57c88912803eab8e777c9e6ef3bcaa5401709381
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/bttestui/Button.qml | 2 | ||||
-rw-r--r-- | tests/bttestui/CMakeLists.txt | 22 | ||||
-rw-r--r-- | tests/bttestui/btlocaldevice.cpp | 556 | ||||
-rw-r--r-- | tests/bttestui/btlocaldevice.h | 59 | ||||
-rw-r--r-- | tests/bttestui/main.cpp | 4 | ||||
-rw-r--r-- | tests/bttestui/main.qml | 552 |
6 files changed, 946 insertions, 249 deletions
diff --git a/tests/bttestui/Button.qml b/tests/bttestui/Button.qml index a358e37e..32695cd5 100644 --- a/tests/bttestui/Button.qml +++ b/tests/bttestui/Button.qml @@ -26,7 +26,7 @@ ** ****************************************************************************/ -import QtQuick 2.0 +import QtQuick Rectangle { diff --git a/tests/bttestui/CMakeLists.txt b/tests/bttestui/CMakeLists.txt index 223cb269..7f1c31fe 100644 --- a/tests/bttestui/CMakeLists.txt +++ b/tests/bttestui/CMakeLists.txt @@ -56,11 +56,23 @@ else() ) endif() +set_target_properties(bttestui PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) -#### Keys ignored in scope 1:.:.:bttestui.pro:<TRUE>: -# OTHER_FILES = "main.qml" "Button.qml" -# TEMPLATE = "app" +if(APPLE) + # Ninja has trouble with relative paths, convert to absolute as a workaround + get_filename_component(SHARED_PLIST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../shared ABSOLUTE) + if(IOS) + set_target_properties(bttestui PROPERTIES + MACOSX_BUNDLE_INFO_PLIST "${SHARED_PLIST_DIR}/Info.ios.plist" + ) + else() + set_target_properties(bttestui PROPERTIES + MACOSX_BUNDLE_INFO_PLIST "${SHARED_PLIST_DIR}/Info.macos.plist" + ) + endif() +endif() -## Scopes: -##################################################################### diff --git a/tests/bttestui/btlocaldevice.cpp b/tests/bttestui/btlocaldevice.cpp index 30dde951..127a03d7 100644 --- a/tests/bttestui/btlocaldevice.cpp +++ b/tests/bttestui/btlocaldevice.cpp @@ -31,6 +31,9 @@ #include <QDebug> #include <QTimer> #include <QtBluetooth/QBluetoothServiceInfo> +#include <QtBluetooth/QLowEnergyCharacteristicData> +#include <QtBluetooth/QLowEnergyDescriptorData> +#include <QtBluetooth/QLowEnergyAdvertisingParameters> #define BTCHAT_DEVICE_ADDR "00:15:83:38:17:C3" @@ -43,6 +46,61 @@ #define SOCKET_PROTOCOL QBluetoothServiceInfo::RfcommProtocol //#define SOCKET_PROTOCOL QBluetoothServiceInfo::L2capProtocol +using namespace Qt::Literals::StringLiterals; +static const auto leServiceUuid = "1ff5e37c-ac16-11eb-ae5c-93d3a763feed"_L1; +static const auto leCharacteristicUuid = "2ff4f68e-ac16-11eb-9956-cfe55a8ccafe"_L1; +// Used for finding a matching LE peripheral device. Typically the default BtTestUi is ok +// when running against macOS/iOS/Linux peripheral, but with Android this needs to be adjusted +// to device's name. We can't use bluetooth address for matching as the public address of the +// peripheral may change +static const auto leRemotePeriphreralDeviceName = "BtTestUi"_L1; +static const qsizetype leCharacteristicSize = 4; // Set to 1...512 bytes +static auto leCharacteristicValue = QByteArray{leCharacteristicSize, 0}; +static quint8 leValueUpdate = 1; + +// String tables to shorten the enum strings to fit the screen estate. +// The values in the tables must be in same order as the corresponding enums +static constexpr const char* controllerStateString[] = { + "Unconnected", + "Connecting", + "Connected", + "Discovering", + "Discovered", + "Closing", + "Advertising", +}; + +static constexpr const char* controllerErrorString[] = { + "None", + "UnknownError", + "UnknownRemDev", + "NetworkError", + "InvAdapter", + "ConnectionErr", + "AdvertisingErr", + "RemHostClosed", + "AuthError", + "MissingPerm", +}; + +static constexpr const char* serviceStateString[] = { + "InvalidService", + "RemoteService", + "RemDiscovering", + "RemDiscovered", + "LocalService", +}; + +static constexpr const char* serviceErrorString[] = { + "None", + "Operation", + "CharWrite", + "DescWrite", + "Unknown", + "CharRead", + "DescRead", +}; + BtLocalDevice::BtLocalDevice(QObject *parent) : QObject(parent), securityFlags(QBluetooth::Security::NoSecurity) { @@ -124,15 +182,17 @@ void BtLocalDevice::setSecFlags(int newFlags) QString BtLocalDevice::hostMode() const { - switch (localDevice->hostMode()) { - case QBluetoothLocalDevice::HostDiscoverable: - return QStringLiteral("HostMode: Discoverable"); - case QBluetoothLocalDevice::HostConnectable: - return QStringLiteral("HostMode: Connectable"); - case QBluetoothLocalDevice::HostDiscoverableLimitedInquiry: - return QStringLiteral("HostMode: DiscoverableLimit"); - case QBluetoothLocalDevice::HostPoweredOff: - return QStringLiteral("HostMode: Powered Off"); + if (localDevice) { + switch (localDevice->hostMode()) { + case QBluetoothLocalDevice::HostDiscoverable: + return QStringLiteral("HostMode: Discoverable"); + case QBluetoothLocalDevice::HostConnectable: + return QStringLiteral("HostMode: Connectable"); + case QBluetoothLocalDevice::HostDiscoverableLimitedInquiry: + return QStringLiteral("HostMode: DiscoverableLimit"); + case QBluetoothLocalDevice::HostPoweredOff: + return QStringLiteral("HostMode: Powered Off"); + } } return QStringLiteral("HostMode: <None>"); @@ -140,17 +200,16 @@ QString BtLocalDevice::hostMode() const void BtLocalDevice::setHostMode(int newMode) { - localDevice->setHostMode(static_cast<QBluetoothLocalDevice::HostMode>(newMode)); + if (localDevice) + localDevice->setHostMode(static_cast<QBluetoothLocalDevice::HostMode>(newMode)); } void BtLocalDevice::requestPairingUpdate(bool isPairing) { QBluetoothAddress baddr(BTCHAT_DEVICE_ADDR); - if (baddr.isNull()) + if (!localDevice || baddr.isNull()) return; - - if (isPairing) { //toggle between authorized and non-authorized pairing to achieve better //level of testing @@ -228,7 +287,11 @@ void BtLocalDevice::deviceDiscovered(const QBluetoothDeviceInfo &info) qDebug() << "Found new device: " << info.name() << info.isValid() << info.address().toString() << info.rssi() << info.majorDeviceClass() << info.minorDeviceClass() << services; - + // With LE we match the device by its name as the public bluetooth address can change + if (info.name() == leRemotePeriphreralDeviceName) { + qDebug() << "#### Matching LE peripheral device found"; + leRemotePeripheralDevice = info; + } } void BtLocalDevice::discoveryFinished() @@ -742,6 +805,8 @@ void BtLocalDevice::dumpServerInformation() void BtLocalDevice::dumpInformation() { + if (!localDevice) + return; qDebug() << "###### default local device"; dumpLocalDevice(localDevice); const QList<QBluetoothHostInfo> list = QBluetoothLocalDevice::allDevices(); @@ -795,10 +860,14 @@ void BtLocalDevice::dumpInformation() QBluetoothServiceDiscoveryAgent validSAgent(localDevice->address()); validSAgent.start(); qDebug() << "######" << (validSAgent.error() == QBluetoothServiceDiscoveryAgent::NoError) << "(Expected: true)"; + + dumpLeInfo(); } void BtLocalDevice::powerOn() { + if (!localDevice) + return; qDebug() << "Powering on"; localDevice->powerOn(); } @@ -819,3 +888,462 @@ void BtLocalDevice::dumpLocalDevice(QBluetoothLocalDevice *dev) qDebug() << " Address" << dev->address().toString(); qDebug() << " HostMode" << dev->hostMode(); } + +void BtLocalDevice::peripheralCreate() +{ + qDebug() << "######" << "LE create peripheral"; + if (lePeripheralController) { + qDebug() << "Peripheral already existed"; + return; + } + + lePeripheralController.reset(QLowEnergyController::createPeripheral()); + emit leChanged(); + + QObject::connect(lePeripheralController.get(), &QLowEnergyController::errorOccurred, + [this](QLowEnergyController::Error error) { + qDebug() << "QLowEnergyController peripheral errorOccurred:" << error; + emit leChanged(); + }); + QObject::connect(lePeripheralController.get(), &QLowEnergyController::stateChanged, + [this](QLowEnergyController::ControllerState state) { + qDebug() << "QLowEnergyController peripheral stateChanged:" << state; + emit leChanged(); + }); +} + +void BtLocalDevice::peripheralStartAdvertising() +{ + qDebug() << "######" << "LE start advertising"; + if (!lePeripheralController) { + qDebug() << "Create peripheral first"; + return; + } + + if (!leServiceData.isValid()) { + // Create service data if this is the first advertisement + leServiceData.setType(QLowEnergyServiceData::ServiceTypePrimary); + leServiceData.setUuid(QBluetoothUuid(leServiceUuid)); + + QLowEnergyCharacteristicData charData; + charData.setUuid(QBluetoothUuid(leCharacteristicUuid)); + charData.setValue(leCharacteristicValue); + charData.setValueLength(leCharacteristicSize, leCharacteristicSize); + charData.setProperties(QLowEnergyCharacteristic::PropertyType::Read + | QLowEnergyCharacteristic::PropertyType::Write + | QLowEnergyCharacteristic::PropertyType::Notify); + + const QLowEnergyDescriptorData clientConfig( + QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration, + QLowEnergyCharacteristic::CCCDDisable); + charData.addDescriptor(clientConfig); + leServiceData.addCharacteristic(charData); + + // Create advertisement data + leAdvertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral); + leAdvertisingData.setIncludePowerLevel(true); + leAdvertisingData.setLocalName(leRemotePeriphreralDeviceName); + // Results in too big advertisement data, can be useful for testing such scenario + // leAdvertisingData.setServices(QList{leServiceData.uuid()}); + } + + // Add/create the service + lePeripheralService.reset(lePeripheralController->addService(leServiceData)); + emit leChanged(); + if (!lePeripheralService) { + qDebug() << "Peripheral service creation failed"; + return; + } + QObject::connect(lePeripheralService.get(), &QLowEnergyService::characteristicWritten, + [](const QLowEnergyCharacteristic&, const QByteArray& value){ + qDebug() << "LE peripheral service characteristic value written" << value; + }); + QObject::connect(lePeripheralService.get(), &QLowEnergyService::characteristicRead, + [](const QLowEnergyCharacteristic&, const QByteArray& value){ + qDebug() << "LE peripheral service characteristic value read" << value; + }); + QObject::connect(lePeripheralService.get(), &QLowEnergyService::characteristicChanged, + [](const QLowEnergyCharacteristic&, const QByteArray& value){ + qDebug() << "LE peripheral service characteristic value changed" << value; + }); + QObject::connect(lePeripheralService.get(), &QLowEnergyService::errorOccurred, + [this](QLowEnergyService::ServiceError error){ + qDebug() << "LE peripheral service errorOccurred:" << error; + emit leChanged(); + }); + QObject::connect(lePeripheralService.get(), &QLowEnergyService::stateChanged, + [this](QLowEnergyService::ServiceState state){ + qDebug() << "LE peripheral service state changed:" << state; + emit leChanged(); + }); + + // Start advertising + lePeripheralController->startAdvertising(QLowEnergyAdvertisingParameters{}, + leAdvertisingData, leAdvertisingData); +} + +void BtLocalDevice::peripheralStopAdvertising() +{ + qDebug() << "######" << "LE stop advertising"; + if (!lePeripheralController) { + qDebug() << "Peripheral does not exist"; + return; + } + lePeripheralController->stopAdvertising(); +} + +void BtLocalDevice::centralWrite() +{ + qDebug() << "######" << "LE central write"; + if (!leCentralController || !leCentralService) { + qDebug() << "Central or central service does not exist"; + return; + } + // Update value at the beginning and end so we can check whole data is sent in large writes. + // Value is offset'd with 100 to easily see which end did the write when testing + leValueUpdate += 1; + leCharacteristicValue[0] = leValueUpdate + 100; + leCharacteristicValue[leCharacteristicSize - 1] = leValueUpdate + 100; + auto characteristic = leCentralService->characteristic(QBluetoothUuid(leCharacteristicUuid)); + if (characteristic.isValid()) { + qDebug() << " Central writes value:" << leCharacteristicValue; + leCentralService->writeCharacteristic(characteristic, leCharacteristicValue); + } else { + qDebug() << "Characteristic was invalid"; + } +} + +void BtLocalDevice::centralRead() +{ + qDebug() << "######" << "LE central read"; + if (!leCentralController || !leCentralService) { + qDebug() << "Central or central service does not exist"; + return; + } + auto characteristic = leCentralService->characteristic(QBluetoothUuid(leCharacteristicUuid)); + if (characteristic.isValid()) { + qDebug() << " Value before issuing read():" << characteristic.value(); + leCentralService->readCharacteristic(characteristic); + } else { + qDebug() << "Characteristic was invalid"; + } +} + +void BtLocalDevice::peripheralWrite() +{ + qDebug() << "######" << "LE peripheral write"; + if (!lePeripheralController || !lePeripheralService) { + qDebug() << "Peripheral or peripheral service does not exist"; + return; + } + // Update value at the beginning and end so we can check whole data is sent in large writes + leCharacteristicValue[0] = ++leValueUpdate; + leCharacteristicValue[leCharacteristicSize - 1] = leValueUpdate; + auto characteristic = lePeripheralService->characteristic(QBluetoothUuid(leCharacteristicUuid)); + if (characteristic.isValid()) { + qDebug() << " Peripheral writes value:" << leCharacteristicValue; + lePeripheralService->writeCharacteristic(characteristic, leCharacteristicValue); + } else { + qDebug() << "Characteristic was invalid"; + } +} + +void BtLocalDevice::peripheralRead() +{ + qDebug() << "######" << "LE peripheral read"; + if (!lePeripheralController || !lePeripheralService) { + qDebug() << "Peripheral or peripheral service does not exist"; + return; + } + auto characteristic = lePeripheralService->characteristic(QBluetoothUuid(leCharacteristicUuid)); + if (characteristic.isValid()) + qDebug() << " Value:" << characteristic.value(); + else + qDebug() << "Characteristic was invalid"; +} + +void BtLocalDevice::startLeDeviceDiscovery() +{ + qDebug() << "######" << "LE device discovery start for:" << leRemotePeriphreralDeviceName; + if (deviceAgent) + deviceAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); +} + +void BtLocalDevice::centralStartServiceDiscovery() +{ + qDebug() << "######" << "LE service discovery start"; + if (!leCentralController) { + qDebug() << "Create and connect central first"; + return; + } + leCentralController->discoverServices(); +} + +void BtLocalDevice::centralCreate() +{ + qDebug() << "######" << "Central create"; + if (leCentralController) { + qDebug() << "Central already existed"; + return; + } + + if (!leRemotePeripheralDevice.isValid()) { + qDebug() << "Creation failed, needs successful LE device discovery first"; + return; + } + + leCentralController.reset(QLowEnergyController::createCentral(leRemotePeripheralDevice)); + emit leChanged(); + + if (!leCentralController) { + qDebug() << "LE Central creation failed"; + return; + } + + QObject::connect(leCentralController.get(), &QLowEnergyController::errorOccurred, + [](QLowEnergyController::Error error) { + qDebug() << "QLowEnergyController peripheral errorOccurred:" << error; + }); + QObject::connect(leCentralController.get(), &QLowEnergyController::stateChanged, + [this](QLowEnergyController::ControllerState state) { + qDebug() << "QLowEnergyController peripheral stateChanged:" << state; + emit leChanged(); + }); +} + +void BtLocalDevice::centralDiscoverServiceDetails() +{ + qDebug() << "###### Discover Service details"; + if (!leCentralController) { + qDebug() << "Central does not exist"; + return; + } + leCentralService.reset( + leCentralController->createServiceObject(QBluetoothUuid(leServiceUuid))); + emit leChanged(); + if (!leCentralService) { + qDebug() << "Service creation failed, cannot discover details"; + return; + } + QObject::connect(leCentralService.get(), &QLowEnergyService::stateChanged, + [this](QLowEnergyService::ServiceState state){ + qDebug() << "LE central service state changed:" << state; + emit leChanged(); + }); + QObject::connect(leCentralService.get(), &QLowEnergyService::characteristicWritten, + [](const QLowEnergyCharacteristic&, const QByteArray& value){ + qDebug() << "LE central service characteristic value written" << value; + }); + QObject::connect(leCentralService.get(), &QLowEnergyService::characteristicRead, + [](const QLowEnergyCharacteristic&, const QByteArray& value){ + qDebug() << "LE central service characteristic value read" << value; + }); + QObject::connect(leCentralService.get(), &QLowEnergyService::characteristicChanged, + [](const QLowEnergyCharacteristic&, const QByteArray& value){ + qDebug() << "LE central service characteristic value changed" << value; + }); + QObject::connect(leCentralService.get(), &QLowEnergyService::descriptorWritten, + [this](const QLowEnergyDescriptor&, const QByteArray& value){ + qDebug() << "LE central service descriptor value written" << value; + emit leChanged(); + }); + QObject::connect(leCentralService.get(), &QLowEnergyService::errorOccurred, + [](QLowEnergyService::ServiceError error){ + qDebug() << "LE central service error occurred:" << error; + }); + leCentralService->discoverDetails(QLowEnergyService::FullDiscovery); +} + +void BtLocalDevice::centralConnect() +{ + qDebug() << "######" << "Central connect"; + if (!leCentralController) { + qDebug() << "Create central first"; + return; + } + leCentralController->connectToDevice(); +} + +void BtLocalDevice::dumpLeInfo() +{ + const auto controllerDump = [](QLowEnergyController* controller) { + qDebug() << " State:" << controller->state(); + qDebug() << " Role:" << controller->role(); + qDebug() << " Error:" << controller->error(); + qDebug() << " ErrorString:" << controller->errorString(); + qDebug() << " MTU:" << controller->mtu(); + qDebug() << " Local Address:" << controller->localAddress(); + qDebug() << " RemoteAddress:" << controller->remoteAddress(); + qDebug() << " RemoteName:" << controller->remoteName(); + qDebug() << " Services count:" << controller->services().size(); + }; + qDebug() << "######" << "LE Peripheral controller"; + if (lePeripheralController) + controllerDump(lePeripheralController.get()); + + qDebug() << "######" << "LE Central controller"; + if (leCentralController) + controllerDump(leCentralController.get()); + + qDebug() << "######" << "LE Found peripheral device"; + if (leRemotePeripheralDevice.isValid()) { + qDebug() << " Name:" << leRemotePeripheralDevice.name(); + qDebug() << " UUID:" << leRemotePeripheralDevice.deviceUuid(); + qDebug() << " Address:" << leRemotePeripheralDevice.address(); + } + + const auto serviceDump = [](QLowEnergyService* service){ + qDebug() << " Name:" << service->serviceName(); + qDebug() << " Uuid:" << service->serviceUuid(); + qDebug() << " Error:" << service->error(); + auto characteristics = service->characteristics(); + for (const auto& characteristic : characteristics) { + qDebug() << " Characteristic"; + qDebug() << " Uuid" << characteristic.uuid(); + qDebug() << " Value" << characteristic.value(); + + } + }; + + qDebug() << "######" << "LE Central-side service"; + if (leCentralService) + serviceDump(leCentralService.get()); + + qDebug() << "######" << "LE Peripheral-side service"; + if (lePeripheralService) + serviceDump(lePeripheralService.get()); +} + +void BtLocalDevice::centralSubscribeUnsubscribe() +{ + qDebug() << "######" << "LE Central (Un)Subscribe"; + if (!leCentralService) { + qDebug() << "Service object does not exist"; + return; + } + auto characteristic = leCentralService->characteristic(QBluetoothUuid(leCharacteristicUuid)); + if (!characteristic.isValid()) { + qDebug() << "Characteristic is not valid"; + return; + } + + auto descriptor = characteristic.descriptor( + QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration); + if (!descriptor.isValid()) { + qDebug() << "Descriptor is not valid"; + return; + } + if (descriptor.value() == QByteArray::fromHex("0000")) { + qDebug() << " Subscribing notifications"; + leCentralService->writeDescriptor(descriptor, QByteArray::fromHex("0100")); + } else { + qDebug() << " Unsubscribing notifications"; + leCentralService->writeDescriptor(descriptor, QByteArray::fromHex("0000")); + } + emit leChanged(); +} + +void BtLocalDevice::centralDelete() +{ + qDebug() << "######" << "Delete central" << leCentralController.get(); + leCentralController.reset(nullptr); + emit leChanged(); +} + +void BtLocalDevice::peripheralDelete() +{ + qDebug() << "######" << "Delete peripheral" << lePeripheralController.get(); + lePeripheralController.reset(nullptr); + emit leChanged(); +} + +bool BtLocalDevice::centralExists() const +{ + return leCentralController.get(); +} + +bool BtLocalDevice::centralSubscribed() const +{ + if (!leCentralService) + return false; + + auto characteristic = leCentralService->characteristic(QBluetoothUuid(leCharacteristicUuid)); + if (!characteristic.isValid()) + return false; + + auto descriptor = characteristic.descriptor( + QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration); + if (!descriptor.isValid()) + return false; + + return (descriptor.value() != QByteArray::fromHex("0000")); +} + +QByteArray BtLocalDevice::centralState() const +{ + if (!leCentralController) + return "(N/A)"_ba; + + return controllerStateString[leCentralController->state()]; +} + +QByteArray BtLocalDevice::centralServiceState() const +{ + if (!leCentralService) + return "(N/A)"_ba; + + return serviceStateString[leCentralService->state()]; +} + +QByteArray BtLocalDevice::centralError() const +{ + if (!leCentralController) + return "(N/A)"_ba; + + return controllerErrorString[leCentralController->error()]; +} + +QByteArray BtLocalDevice::centralServiceError() const +{ + if (!leCentralService) + return "(N/A)"_ba; + + return serviceErrorString[leCentralService->error()]; +} + +QByteArray BtLocalDevice::peripheralState() const +{ + if (!lePeripheralController) + return "(N/A)"_ba; + + return controllerStateString[lePeripheralController->state()]; +} + +QByteArray BtLocalDevice::peripheralServiceState() const +{ + if (!lePeripheralService) + return "(N/A)"_ba; + + return serviceStateString[lePeripheralService->state()]; +} + +QByteArray BtLocalDevice::peripheralError() const +{ + if (!lePeripheralController) + return "(N/A)"_ba; + + return controllerErrorString[lePeripheralController->error()]; +} + +QByteArray BtLocalDevice::peripheralServiceError() const +{ + if (!lePeripheralService) + return "(N/A)"_ba; + + return serviceErrorString[lePeripheralService->error()]; +} + +bool BtLocalDevice::peripheralExists() const +{ + return lePeripheralController.get(); +} diff --git a/tests/bttestui/btlocaldevice.h b/tests/bttestui/btlocaldevice.h index 44821970..5917a442 100644 --- a/tests/bttestui/btlocaldevice.h +++ b/tests/bttestui/btlocaldevice.h @@ -35,6 +35,8 @@ #include <QtBluetooth/QBluetoothServer> #include <QtBluetooth/QBluetoothServiceDiscoveryAgent> #include <QtBluetooth/QBluetoothSocket> +#include <QtBluetooth/QLowEnergyController> +#include <QtBluetooth/QLowEnergyServiceData> class BtLocalDevice : public QObject { @@ -43,8 +45,18 @@ public: explicit BtLocalDevice(QObject *parent = 0); ~BtLocalDevice(); Q_PROPERTY(QString hostMode READ hostMode NOTIFY hostModeStateChanged) - Q_PROPERTY(int secFlags READ secFlags WRITE setSecFlags - NOTIFY secFlagsChanged) + Q_PROPERTY(int secFlags READ secFlags WRITE setSecFlags NOTIFY secFlagsChanged) + Q_PROPERTY(bool centralExists READ centralExists NOTIFY leChanged); + Q_PROPERTY(bool centralSubscribed READ centralSubscribed NOTIFY leChanged); + Q_PROPERTY(QByteArray centralState READ centralState NOTIFY leChanged); + Q_PROPERTY(QByteArray centralError READ centralError NOTIFY leChanged); + Q_PROPERTY(QByteArray centralServiceState READ centralServiceState NOTIFY leChanged); + Q_PROPERTY(QByteArray centralServiceError READ centralServiceError NOTIFY leChanged); + Q_PROPERTY(QByteArray peripheralState READ peripheralState NOTIFY leChanged); + Q_PROPERTY(QByteArray peripheralError READ peripheralError NOTIFY leChanged); + Q_PROPERTY(QByteArray peripheralServiceState READ peripheralServiceState NOTIFY leChanged); + Q_PROPERTY(QByteArray peripheralServiceError READ peripheralServiceError NOTIFY leChanged); + Q_PROPERTY(bool peripheralExists READ peripheralExists NOTIFY leChanged); QBluetooth::SecurityFlags secFlags() const; void setSecFlags(int); @@ -55,6 +67,7 @@ signals: void hostModeStateChanged(); void socketStateUpdate(int foobar); void secFlagsChanged(); + bool leChanged(); // Same signal used for LE changes for simplicity public slots: //QBluetoothLocalDevice @@ -110,6 +123,39 @@ public slots: void clientSocketReadyRead(); void dumpServerInformation(); + //QLowEnergyController central + void centralCreate(); + void centralConnect(); + void centralStartServiceDiscovery(); + void centralDiscoverServiceDetails(); + void centralWrite(); + void centralRead(); + void centralSubscribeUnsubscribe(); + void centralDelete(); + bool centralExists() const; + bool centralSubscribed() const; + QByteArray centralState() const; + QByteArray centralServiceState() const; + QByteArray centralError() const; + QByteArray centralServiceError() const; + + //QLowEnergyController peripheral + void peripheralCreate(); + void peripheralStartAdvertising(); + void peripheralStopAdvertising(); + void peripheralWrite(); + void peripheralRead(); + void peripheralDelete(); + bool peripheralExists() const; + QByteArray peripheralState() const; + QByteArray peripheralServiceState() const; + QByteArray peripheralError() const; + QByteArray peripheralServiceError() const; + + // QLowEnergyController misc + void startLeDeviceDiscovery(); + void dumpLeInfo(); + private: void dumpLocalDevice(QBluetoothLocalDevice *dev); @@ -120,9 +166,16 @@ private: QBluetoothServer *server = nullptr; QList<QBluetoothSocket *> serverSockets; QBluetoothServiceInfo serviceInfo; - QList<QBluetoothServiceInfo> foundTestServers; QBluetooth::SecurityFlags securityFlags; + + std::unique_ptr<QLowEnergyController> leCentralController; + std::unique_ptr<QLowEnergyController> lePeripheralController; + std::unique_ptr<QLowEnergyService> lePeripheralService; + std::unique_ptr<QLowEnergyService> leCentralService; + QLowEnergyAdvertisingData leAdvertisingData; + QLowEnergyServiceData leServiceData; + QBluetoothDeviceInfo leRemotePeripheralDevice; }; #endif // BTLOCALDEVICE_H diff --git a/tests/bttestui/main.cpp b/tests/bttestui/main.cpp index 35f95b6e..51587424 100644 --- a/tests/bttestui/main.cpp +++ b/tests/bttestui/main.cpp @@ -39,12 +39,10 @@ int main(int argc, char *argv[]) { QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true")); QGuiApplication app(argc, argv); - qmlRegisterType<BtLocalDevice>("Local", 5, 2, "BluetoothDevice"); + qmlRegisterType<BtLocalDevice>("Local", 6, 5, "BluetoothDevice"); QQuickView view; view.setSource(QStringLiteral("qrc:///main.qml")); - view.setWidth(550); - view.setHeight(550); view.setResizeMode(QQuickView::SizeRootObjectToView); QObject::connect(view.engine(), SIGNAL(quit()), qApp, SLOT(quit())); diff --git a/tests/bttestui/main.qml b/tests/bttestui/main.qml index 05f57fa6..9c4493d6 100644 --- a/tests/bttestui/main.qml +++ b/tests/bttestui/main.qml @@ -26,244 +26,350 @@ ** ****************************************************************************/ -import QtQuick 2.0 -import Local 5.2 +import QtQuick +import Local -Rectangle { - width: 360 - height: 360 +Flickable { + width: content.width + height: content.height + contentWidth: content.width + contentHeight: content.height - BluetoothDevice { - id: device - function evaluateError(error) - { - switch (error) { - case 0: return "Last Error: NoError" - case 1: return "Last Error: Pairing Error" - case 100: return "Last Error: Unknown Error" - case 1000: return "Last Error: <None>" - } - } + Rectangle { + id: content + width: mainRow.implicitWidth + height: mainRow.implicitHeight - function evaluateSocketState(s) { - switch (s) { - case 0: return "Socket: Unconnected"; - case 1: return "Socket: HostLookup"; - case 2: return "Socket: Connecting"; - case 3: return "Socket: Connected"; - case 4: return "Socket: Bound"; - case 5: return "Socket: Listening"; - case 6: return "Socket: Closing"; + BluetoothDevice { + id: device + function evaluateError(error) + { + switch (error) { + case 0: return "Last Error: NoError" + case 1: return "Last Error: Pairing Error" + case 100: return "Last Error: Unknown Error" + case 1000: return "Last Error: <None>" + } } - return "Socket: <None>"; - } + function evaluateSocketState(s) { + switch (s) { + case 0: return "Socket: Unconnected"; + case 1: return "Socket: HostLookup"; + case 2: return "Socket: Connecting"; + case 3: return "Socket: Connected"; + case 4: return "Socket: Bound"; + case 5: return "Socket: Listening"; + case 6: return "Socket: Closing"; + } + return "Socket: <None>"; + } - onErrorOccurred: errorText.text = evaluateError(error) - onHostModeStateChanged: hostModeText.text = device.hostMode; - onSocketStateUpdate : socketStateText.text = evaluateSocketState(foobar); - } - - Text { - id: errorText - anchors.left: parent.left - anchors.bottom: parent.bottom - anchors.bottomMargin: 3 - text: "Last Error: <None>" - } - Text { - id: hostModeText - anchors.left: parent.left - anchors.bottom: socketStateText.top - anchors.bottomMargin: 3 - text: device.hostMode - } - Text { - id: socketStateText - anchors.left: parent.left - anchors.bottom: errorText.top - anchors.bottomMargin: 3 - text: device.evaluateSocketState(0) - } - Text { - id: secFlagLabel; text: "SecFlags: " - anchors.left: parent.left - anchors.bottom: hostModeText.top - anchors.bottomMargin: 3 - } - Text { - anchors.left: secFlagLabel.right - anchors.bottom: hostModeText.top - anchors.bottomMargin: 3 - text: device.secFlags - } - Row { - anchors.top: parent.top - anchors.left: parent.left - anchors.margins: 4 + onErrorOccurred: (error) => errorText.text = evaluateError(error) + onHostModeStateChanged: hostModeText.text = device.hostMode; + onSocketStateUpdate: (foobar) => socketStateText.text = evaluateSocketState(foobar); + } - spacing: 8 - Column { - spacing: 8 - Text{ - width: connectBtn.width - horizontalAlignment: Text.AlignLeft - font.pointSize: 12 - wrapMode: Text.WordWrap - text: "Device Management" - } - Button { + Row { + id: mainRow + anchors.top: parent.top + anchors.left: parent.left + anchors.margins: 4 - buttonText: "PowerOn" - onClicked: device.powerOn() - } - Button { - buttonText: "PowerOff" - onClicked: device.setHostMode(0) - } - Button { - id: connectBtn - buttonText: "Connectable" - onClicked: device.setHostMode(1) - } - Button { - buttonText: "Discover" - onClicked: device.setHostMode(2) - } - Button { - buttonText: "Pair" - onClicked: device.requestPairingUpdate(true) - } - Button { - buttonText: "Unpair" - onClicked: device.requestPairingUpdate(false) - } - Button { - buttonText: "Cycle SecFlag" - onClicked: device.cycleSecurityFlags() - } - } - Column { spacing: 8 - Text{ - width: startFullSDiscBtn.width - horizontalAlignment: Text.AlignLeft - font.pointSize: 12 - wrapMode: Text.WordWrap - text: "Device & Service Discovery" - } - Button { - buttonText: "StartDDisc" - onClicked: device.startDiscovery() - } - Button { - buttonText: "StopDDisc" - onClicked: device.stopDiscovery() - } - Button { - buttonText: "StartMinSDisc" - onClicked: device.startServiceDiscovery(true) - } - Button { - id: startFullSDiscBtn - buttonText: "StartFullSDisc" - onClicked: device.startServiceDiscovery(false) - } - Button { - id: startRemoteSDiscBtn - buttonText: "StartRemSDisc" - onClicked: device.startTargettedServiceDiscovery() - } - Button { - buttonText: "StopSDisc" - onClicked: device.stopServiceDiscovery(); - } - Button { - buttonText: "DumpSDisc" - onClicked: device.dumpServiceDiscovery(); - } + Column { + spacing: 8 + Text{ + width: connectBtn.width + horizontalAlignment: Text.AlignLeft + font.pointSize: 12 + wrapMode: Text.WordWrap + text: "Device Management" + } + Button { - } + buttonText: "PowerOn" + onClicked: device.powerOn() + } + Button { + buttonText: "PowerOff" + onClicked: device.setHostMode(0) + } + Button { + id: connectBtn + buttonText: "Connectable" + onClicked: device.setHostMode(1) + } + Button { + buttonText: "Discover" + onClicked: device.setHostMode(2) + } + Button { + buttonText: "Pair" + onClicked: device.requestPairingUpdate(true) + } + Button { + buttonText: "Unpair" + onClicked: device.requestPairingUpdate(false) + } + Button { + buttonText: "Cycle SecFlag" + onClicked: device.cycleSecurityFlags() + } + Text { + id: errorText + text: "Last Error: <None>" + } + Text { + id: hostModeText + text: device.hostMode + } + Text { + id: socketStateText + text: device.evaluateSocketState(0) + } + Text { + id: secFlagLabel; + text: "SecFlags: " + device.secFlags + } + } + Column { + spacing: 8 + Text{ + width: startFullSDiscBtn.width + horizontalAlignment: Text.AlignLeft + font.pointSize: 12 + wrapMode: Text.WordWrap + text: "Device & Service Discovery" + } + Button { + buttonText: "StartDDisc" + onClicked: device.startDiscovery() + } + Button { + buttonText: "StopDDisc" + onClicked: device.stopDiscovery() + } + Button { + buttonText: "StartMinSDisc" + onClicked: device.startServiceDiscovery(true) + } + Button { + id: startFullSDiscBtn + buttonText: "StartFullSDisc" + onClicked: device.startServiceDiscovery(false) + } + Button { + id: startRemoteSDiscBtn + buttonText: "StartRemSDisc" + onClicked: device.startTargettedServiceDiscovery() + } + Button { + buttonText: "StopSDisc" + onClicked: device.stopServiceDiscovery(); + } + Button { + buttonText: "DumpSDisc" + onClicked: device.dumpServiceDiscovery(); + } - Column { - spacing: 8 - Text{ - width: connectSearchBtn.width - horizontalAlignment: Text.AlignLeft - font.pointSize: 12 - wrapMode: Text.WordWrap - text: "Client & Server Socket" - } - Button { - buttonText: "SocketDump" - onClicked: device.dumpSocketInformation() - } - Button { - buttonText: "CConnect" - onClicked: device.connectToService() - } - Button { - id: connectSearchBtn - buttonText: "ConnectSearch" - onClicked: device.connectToServiceViaSearch() - } - Button { - buttonText: "CDisconnect" - onClicked: device.disconnectToService() } - Button { - buttonText: "CClose" - onClicked: device.closeSocket() - } + Column { + spacing: 8 + Text{ + width: connectSearchBtn.width + horizontalAlignment: Text.AlignLeft + font.pointSize: 12 + wrapMode: Text.WordWrap + text: "Client & Server Socket" + } + Button { + buttonText: "SocketDump" + onClicked: device.dumpSocketInformation() + } + Button { + buttonText: "CConnect" + onClicked: device.connectToService() + } + Button { + id: connectSearchBtn + buttonText: "ConnectSearch" + onClicked: device.connectToServiceViaSearch() + } + Button { + buttonText: "CDisconnect" + onClicked: device.disconnectToService() + } - Button { - buttonText: "CAbort" - onClicked: device.abortSocket() - } - Button { - //Write to all open sockets ABCABC\n - buttonText: "CSWrite" - onClicked: device.writeData() - } - Button { - buttonText: "ServerDump" - onClicked: device.dumpServerInformation() - } - Button { - //Listen on server via port - buttonText: "SListenPort" - onClicked: device.serverListenPort() - } - Button { - //Listen on server via uuid - buttonText: "SListenUuid" - onClicked: device.serverListenUuid() - } - Button { - //Close Bluetooth server - buttonText: "SClose" - onClicked: device.serverClose() - } - } - Column { - spacing: 8 - Text{ - width: resetBtn.width - horizontalAlignment: Text.AlignLeft - font.pointSize: 12 - wrapMode: Text.WordWrap - text: "Misc Controls" - } - Button { - buttonText: "Dump" - onClicked: device.dumpInformation() - } - Button { - id: resetBtn - buttonText: "Reset" - onClicked: device.reset() + Button { + buttonText: "CClose" + onClicked: device.closeSocket() + } + + Button { + buttonText: "CAbort" + onClicked: device.abortSocket() + } + Button { + //Write to all open sockets ABCABC\n + buttonText: "CSWrite" + onClicked: device.writeData() + } + Button { + buttonText: "ServerDump" + onClicked: device.dumpServerInformation() + } + Button { + //Listen on server via port + buttonText: "SListenPort" + onClicked: device.serverListenPort() + } + Button { + //Listen on server via uuid + buttonText: "SListenUuid" + onClicked: device.serverListenUuid() + } + Button { + //Close Bluetooth server + buttonText: "SClose" + onClicked: device.serverClose() + } + } + Column { + spacing: 8 + Text{ + width: centralBtn.width + horizontalAlignment: Text.AlignLeft + color: device.centralExists ? "blue" : "black" + font.bold: device.centralExists + font.pointSize: 12 + wrapMode: Text.WordWrap + text: "Low Energy Central Controller" + } + Rectangle { + color: "lightsteelblue" + width: parent.width + height: centralInfo.height + clip: true + Column { + id: centralInfo + Text { text: "CState:" + device.centralState } + Text { text: "CError:" + device.centralError } + Text { text: "SState:" + device.centralServiceState } + Text { text: "SError:" + device.centralServiceError } + Text { text: "Subscribed: " + device.centralSubscribed } + } + } + // The ordinal numbers below indicate the typical sequence + Button { + id: centralBtn + buttonText: "1 DeviceDiscovery" + onClicked: device.startLeDeviceDiscovery() + } + Button { + buttonText: "2 CreateCentral" + onClicked: device.centralCreate() + } + Button { + buttonText: "3 ConnectCentral" + onClicked: device.centralConnect() + } + Button { + buttonText: "4 ServiceDiscovery" + onClicked: device.centralStartServiceDiscovery() + } + Button { + buttonText: "5 ServiceDetails" + onClicked: device.centralDiscoverServiceDetails(); + } + Button { + buttonText: "CharacteristicRead" + onClicked: device.centralRead() + } + Button { + buttonText: "CharacteristicWrite" + onClicked: device.centralWrite() + } + Button { + buttonText: "Sub/Unsubscribe" + onClicked: device.centralSubscribeUnsubscribe(); + } + Button { + buttonText: "DeleteController" + onClicked: device.centralDelete() + } + } + Column { + spacing: 8 + Text{ + width: centralBtn.width + horizontalAlignment: Text.AlignLeft + color: device.peripheralExists ? "blue" : "black" + font.bold: device.peripheralExists + font.pointSize: 12 + wrapMode: Text.WordWrap + text: "Low Energy Peripheral Controller" + } + Rectangle { + color: "lightsteelblue" + width: parent.width + height: peripheralInfo.height + clip: true + Column { + id: peripheralInfo + Text { text: "CState: " + device.peripheralState } + Text { text: "CError: " + device.peripheralError } + Text { text: "SState: " + device.peripheralServiceState } + Text { text: "SError: " + device.peripheralServiceError } + } + } + Button { + id: peripheralBtn + buttonText: "1 CreatePeripheral" + onClicked: device.peripheralCreate() + } + Button { + id: advertiseBtn + buttonText: "2 StartAdvertise" + onClicked: device.peripheralStartAdvertising() + } + Button { + buttonText: "StopAdvertise" + onClicked: device.peripheralStopAdvertising() + } + Button { + buttonText: "CharacteristicRead" + onClicked: device.peripheralRead() + } + Button { + buttonText: "CharacteristicWrite" + onClicked: device.peripheralWrite() + } + Button { + buttonText: "DeleteController" + onClicked: device.peripheralDelete() + } + } + Column { + spacing: 8 + Text{ + width: resetBtn.width + horizontalAlignment: Text.AlignLeft + font.pointSize: 12 + wrapMode: Text.WordWrap + text: "Misc Controls" + } + Button { + buttonText: "Dump" + onClicked: device.dumpInformation() + } + Button { + id: resetBtn + buttonText: "Reset" + onClicked: device.reset() + } } } } |