diff options
author | Juha Vuolle <juha.vuolle@insta.fi> | 2022-11-21 11:44:43 +0200 |
---|---|---|
committer | Juha Vuolle <juha.vuolle@insta.fi> | 2022-12-07 11:47:43 +0200 |
commit | a79a8de70853f5acc9c8d9a9b10760c2418b31ac (patch) | |
tree | e79f1d5b1f8a7d5405fbc9a957904a9c5024222d /tests | |
parent | 4317cc1aca2097444f5ab139da4a0b4ae1b0d43f (diff) |
Improve bluetooth manual test support
This is a preparatory commit for adding the Bluez DBus peripheral
role support.
Aim is to improve the manual test coverage: add large descriptor
writes, ability to set advertising data multiple times, add more
services, characteristics etc. Also adjust the large attribute
size so that it works with bluez dbus.
Task-number: QTBUG-107510
Change-Id: I5f22880349606feb4f61032b78c62b990329eb69
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/bluetoothtestdevice/bluetoothtestdevice.cpp | 34 | ||||
-rw-r--r-- | tests/bttestui/btlocaldevice.cpp | 345 | ||||
-rw-r--r-- | tests/bttestui/btlocaldevice.h | 19 | ||||
-rw-r--r-- | tests/bttestui/main.qml | 40 | ||||
-rw-r--r-- | tests/manual/qlowenergycontroller/tst_qlowenergycontroller_device.cpp | 85 |
5 files changed, 403 insertions, 120 deletions
diff --git a/tests/bluetoothtestdevice/bluetoothtestdevice.cpp b/tests/bluetoothtestdevice/bluetoothtestdevice.cpp index 62daa942..98da879d 100644 --- a/tests/bluetoothtestdevice/bluetoothtestdevice.cpp +++ b/tests/bluetoothtestdevice/bluetoothtestdevice.cpp @@ -21,8 +21,10 @@ #include <thread> -static const QLatin1String largeCharacteristicServiceUuid("1f85e37c-ac16-11eb-ae5c-93d3a763feed"); -static const QLatin1String largeCharacteristicCharUuid("40e4f68e-ac16-11eb-9956-cfe55a8c370c"); +static const QLatin1String largeAttributeServiceUuid("1f85e37c-ac16-11eb-ae5c-93d3a763feed"); +static const QLatin1String largeAttributeCharUuid("40e4f68e-ac16-11eb-9956-cfe55a8c370c"); +static const QLatin1String largeAttributeDescUuid("44e4f68e-ac16-11eb-9956-cfe55a8c370c"); +static constexpr qsizetype largeAttributeSize{508}; // Size for char and desc values static const QLatin1String platformIdentifierServiceUuid("4a92cb7f-5031-4a09-8304-3e89413f458d"); static const QLatin1String platformIdentifierCharUuid("6b0ecf7c-5f09-4c87-aaab-bb49d5d383aa"); @@ -119,26 +121,36 @@ int main(int argc, char *argv[]) serviceDefinitions << serviceData; } { - // large characteristic service + // large attribute 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. + // This is a service offering a large characteristic and descriptor which can + // be read and written to QLowEnergyServiceData serviceData; serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary); - serviceData.setUuid(QBluetoothUuid(largeCharacteristicServiceUuid)); + serviceData.setUuid(QBluetoothUuid(largeAttributeServiceUuid)); QLowEnergyCharacteristicData charData; - charData.setUuid(QBluetoothUuid(largeCharacteristicCharUuid)); - QByteArray initialValue(512, 0); + charData.setUuid(QBluetoothUuid(largeAttributeCharUuid)); + QByteArray initialValue(largeAttributeSize, 0); initialValue[0] = 0x0b; charData.setValue(initialValue); - charData.setValueLength(512, 512); + charData.setValueLength(largeAttributeSize, largeAttributeSize); charData.setProperties(QLowEnergyCharacteristic::PropertyType::Read | QLowEnergyCharacteristic::PropertyType::Write); - serviceData.addCharacteristic(charData); + QByteArray descInitialValue(largeAttributeSize, 0); + descInitialValue[0] = 0xdd; + QLowEnergyDescriptorData descData( + QBluetoothUuid{largeAttributeDescUuid}, + descInitialValue + ); + descData.setWritePermissions(true); + descData.setReadPermissions(true); + + charData.addDescriptor(descData); + + serviceData.addCharacteristic(charData); serviceDefinitions << serviceData; } diff --git a/tests/bttestui/btlocaldevice.cpp b/tests/bttestui/btlocaldevice.cpp index 5f684b16..9306c482 100644 --- a/tests/bttestui/btlocaldevice.cpp +++ b/tests/bttestui/btlocaldevice.cpp @@ -22,16 +22,28 @@ //#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; +// Main service used for testing read/write/notify +static constexpr auto leServiceUuid{"10f5e37c-ac16-11eb-ae5c-93d3a763feed"_L1}; +static constexpr auto leCharUuid1{"11f4f68e-ac16-11eb-9956-cfe55a8ccafe"_L1}; +static constexpr auto leCharUuid2{"12f4f68e-ac16-11eb-9956-cfe55a8ccafe"_L1}; +// Service for testing the included services +static constexpr auto leSecondServiceUuid{"20f5e37c-ac16-11eb-ae5c-93d3a763feed"_L1}; +static constexpr auto leSecondServiceCharUuid1{"21f4f68e-ac16-11eb-9956-cfe55a8ccafe"_L1}; +// Service for testing the secondary service and other miscellaneous +static constexpr auto leThirdServiceUuid{"30f5e37c-ac16-11eb-ae5c-93d3a763feed"_L1}; +static constexpr auto leThirdServiceCharUuid1{"31f4f68e-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; +static QByteArray leCharacteristicValue = QByteArray{leCharacteristicSize, 1}; +static QByteArray leDescriptorValue = "a descriptor value"_ba; +static auto leSecondCharacteristicValue = QByteArray{leCharacteristicSize, 2}; +static quint8 leCharacteristicValueUpdate = 1; +static char leDescriptorValueUpdate = 'b'; // 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 @@ -810,7 +822,8 @@ void BtLocalDevice::dumpErrors() printError("LE Central"_L1, leCentralController.get()); printError("LE Central Service"_L1, leCentralService.get()); printError("LE Peripheral"_L1, lePeripheralController.get()); - printError("LE Peripheral Service"_L1, lePeripheralService.get()); + if (!lePeripheralServices.isEmpty()) + printError("LE Peripheral Service"_L1, lePeripheralServices[0].get()); printError("Socket"_L1, socket); printError("Server"_L1, server); } @@ -924,85 +937,155 @@ void BtLocalDevice::peripheralCreate() }); } -void BtLocalDevice::peripheralStartAdvertising() +void BtLocalDevice::peripheralAddServices() { - qDebug() << "######" << "LE start advertising"; + qDebug() << "######" << "LE add services"; if (!lePeripheralController) { qDebug() << "Create peripheral first"; return; } + if (lePeripheralServiceData.isEmpty()) { + // Create service data + { + QLowEnergyServiceData sd; + sd.setType(QLowEnergyServiceData::ServiceTypePrimary); + sd.setUuid(QBluetoothUuid(leServiceUuid)); + + QLowEnergyCharacteristicData charData; + charData.setUuid(QBluetoothUuid(leCharUuid1)); + 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); + + const QLowEnergyDescriptorData userDescription( + QBluetoothUuid::DescriptorType::CharacteristicUserDescription, + leDescriptorValue); + charData.addDescriptor(userDescription); + sd.addCharacteristic(charData); + + // Set another characteristic without notifications + QLowEnergyCharacteristicData secondCharData; + secondCharData.setUuid(QBluetoothUuid(leCharUuid2)); + secondCharData.setValue(leSecondCharacteristicValue); + secondCharData.setValueLength(leCharacteristicSize, leCharacteristicSize); + secondCharData.setProperties(QLowEnergyCharacteristic::PropertyType::Read + | QLowEnergyCharacteristic::PropertyType::Write); + sd.addCharacteristic(secondCharData); + lePeripheralServiceData << sd; + } + { + QLowEnergyServiceData sd; + sd.setType(QLowEnergyServiceData::ServiceTypePrimary); + sd.setUuid(QBluetoothUuid(leSecondServiceUuid)); + + QLowEnergyCharacteristicData charData; + charData.setUuid(QBluetoothUuid(leSecondServiceCharUuid1)); + charData.setValue("second service char"_ba); + charData.setProperties(QLowEnergyCharacteristic::PropertyType::Read); + sd.addCharacteristic(charData); + lePeripheralServiceData << sd; + } + { + QLowEnergyServiceData sd; + sd.setType(QLowEnergyServiceData::ServiceTypeSecondary); + sd.setUuid(QBluetoothUuid(leThirdServiceUuid)); + + QLowEnergyCharacteristicData charData; + charData.setUuid(QBluetoothUuid(leThirdServiceCharUuid1)); + charData.setValue("third service char"_ba); + charData.setProperties(QLowEnergyCharacteristic::PropertyType::Read); + sd.addCharacteristic(charData); + lePeripheralServiceData << sd; + } + } - 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); - - leAdvertisingData.setManufacturerData(0xCAFE, "maker"); - // Here we use short unrelated UUID so we can fit both service UUID and manufacturer data - // into the advertisement. This is for testing purposes - leAdvertisingData.setServices( - {QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::AlertNotificationService)}); - // Results in too big advertisement data, can be useful for testing such scenario - // leAdvertisingData.setServices(QList{leServiceData.uuid()}); + Q_ASSERT(lePeripheralServiceData.size() == 3); + // Free previous services if any + lePeripheralServices.clear(); + // Add first service, and then set the first service as the included service for the second + auto service = lePeripheralController->addService(lePeripheralServiceData[0]); + if (service) { + lePeripheralServiceData[1].setIncludedServices({service}); + // Then add the services to controller + lePeripheralServices.emplaceBack(service); + lePeripheralServices.emplaceBack( + lePeripheralController->addService(lePeripheralServiceData[1])); + lePeripheralServices.emplaceBack( + lePeripheralController->addService(lePeripheralServiceData[2])); } - // Add/create the service - lePeripheralService.reset(lePeripheralController->addService(leServiceData)); emit leChanged(); - if (!lePeripheralService) { + + if (lePeripheralServices.isEmpty()) { qDebug() << "Peripheral service creation failed"; return; } - QObject::connect(lePeripheralService.get(), &QLowEnergyService::characteristicWritten, + + QObject::connect(lePeripheralServices[0].get(), &QLowEnergyService::characteristicWritten, [](const QLowEnergyCharacteristic&, const QByteArray& value){ qDebug() << "LE peripheral service characteristic value written" << value; }); - QObject::connect(lePeripheralService.get(), &QLowEnergyService::characteristicRead, + QObject::connect(lePeripheralServices[0].get(), &QLowEnergyService::characteristicRead, [](const QLowEnergyCharacteristic&, const QByteArray& value){ qDebug() << "LE peripheral service characteristic value read" << value; }); - QObject::connect(lePeripheralService.get(), &QLowEnergyService::characteristicChanged, + QObject::connect(lePeripheralServices[0].get(), &QLowEnergyService::characteristicChanged, [](const QLowEnergyCharacteristic&, const QByteArray& value){ qDebug() << "LE peripheral service characteristic value changed" << value; }); - QObject::connect(lePeripheralService.get(), &QLowEnergyService::errorOccurred, + QObject::connect(lePeripheralServices[0].get(), &QLowEnergyService::descriptorRead, + [](const QLowEnergyDescriptor&, const QByteArray& value){ + qDebug() << "LE peripheral service descriptor value read" << value; + }); + QObject::connect(lePeripheralServices[0].get(), &QLowEnergyService::descriptorWritten, + [](const QLowEnergyDescriptor&, const QByteArray& value){ + qDebug() << "LE peripheral service descriptor value written" << value; + }); + QObject::connect(lePeripheralServices[0].get(), &QLowEnergyService::errorOccurred, [this](QLowEnergyService::ServiceError error){ qDebug() << "LE peripheral service errorOccurred:" << error; emit leChanged(); }); - QObject::connect(lePeripheralService.get(), &QLowEnergyService::stateChanged, + QObject::connect(lePeripheralServices[0].get(), &QLowEnergyService::stateChanged, [this](QLowEnergyService::ServiceState state){ qDebug() << "LE peripheral service state changed:" << state; emit leChanged(); }); +} + +void BtLocalDevice::peripheralStartAdvertising() +{ + qDebug() << "######" << "LE start advertising"; + if (!lePeripheralController) { + qDebug() << "Create peripheral first"; + return; + } - // Start advertising - QLowEnergyAdvertisingParameters advertisingParameters; - advertisingParameters.setInterval(30, 60); + if (leAdvertisingData.localName().isEmpty()) { + // Create advertisement data + leAdvertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral); + leAdvertisingData.setIncludePowerLevel(true); + leAdvertisingData.setLocalName(leRemotePeriphreralDeviceName); - lePeripheralController->startAdvertising(advertisingParameters, + leAdvertisingData.setManufacturerData(0xCAFE, "maker"); + // Here we use short unrelated UUID so we can fit both service UUID and manufacturer data + // into the advertisement. This is for testing purposes + leAdvertisingData.setServices( + {QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::AlertNotificationService)}); + // May result in too big advertisement data, can be useful for testing such scenario + // leAdvertisingData.setServices({QBluetoothUuid(leServiceUuid)}); + } + // Start advertising. For testing the advertising can be started without valid services + qDebug() << "Starting advertising, services are valid:" << !lePeripheralServiceData.isEmpty(); + lePeripheralController->startAdvertising(QLowEnergyAdvertisingParameters{}, leAdvertisingData, leAdvertisingData); - //lePeripheralController->startAdvertising(QLowEnergyAdvertisingParameters{}, - // leAdvertisingData, leAdvertisingData); } void BtLocalDevice::peripheralStopAdvertising() @@ -1015,20 +1098,20 @@ void BtLocalDevice::peripheralStopAdvertising() lePeripheralController->stopAdvertising(); } -void BtLocalDevice::centralWrite() +void BtLocalDevice::centralCharacteristicWrite() { - qDebug() << "######" << "LE central write"; + qDebug() << "######" << "LE central write characteristic"; 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)); + auto characteristic = leCentralService->characteristic(QBluetoothUuid(leCharUuid1)); if (characteristic.isValid()) { + // 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 + leCharacteristicValueUpdate += 1; + leCharacteristicValue[0] = leCharacteristicValueUpdate + 100; + leCharacteristicValue[leCharacteristicSize - 1] = leCharacteristicValueUpdate + 100; qDebug() << " Central writes value:" << leCharacteristicValue; leCentralService->writeCharacteristic(characteristic, leCharacteristicValue); } else { @@ -1036,14 +1119,14 @@ void BtLocalDevice::centralWrite() } } -void BtLocalDevice::centralRead() +void BtLocalDevice::centralCharacteristicRead() { - qDebug() << "######" << "LE central read"; + qDebug() << "######" << "LE central read characteristic"; if (!leCentralController || !leCentralService) { qDebug() << "Central or central service does not exist"; return; } - auto characteristic = leCentralService->characteristic(QBluetoothUuid(leCharacteristicUuid)); + auto characteristic = leCentralService->characteristic(QBluetoothUuid(leCharUuid1)); if (characteristic.isValid()) { qDebug() << " Value before issuing read():" << characteristic.value(); leCentralService->readCharacteristic(characteristic); @@ -1052,39 +1135,111 @@ void BtLocalDevice::centralRead() } } -void BtLocalDevice::peripheralWrite() +void BtLocalDevice::centralDescriptorWrite() { - qDebug() << "######" << "LE peripheral write"; - if (!lePeripheralController || !lePeripheralService) { + qDebug() << "######" << "LE central write descriptor"; + if (!leCentralController || !leCentralService) { + qDebug() << "Central or central service does not exist"; + return; + } + auto descriptor = leCentralService->characteristic(QBluetoothUuid(leCharUuid1)) + .descriptor(QBluetoothUuid::DescriptorType::CharacteristicUserDescription); + + if (descriptor.isValid()) { + leDescriptorValue[0] = leDescriptorValueUpdate++; + qDebug() << " Central writes value: " << leDescriptorValue; + leCentralService->writeDescriptor(descriptor, leDescriptorValue); + } else { + qDebug() << "Descriptor was invalid"; + } +} + +void BtLocalDevice::centralDescriptorRead() +{ + qDebug() << "######" << "LE central read descriptor"; + if (!leCentralController || !leCentralService) { + qDebug() << "Central or central service does not exist"; + return; + } + auto descriptor = leCentralService->characteristic(QBluetoothUuid(leCharUuid1)) + .descriptor(QBluetoothUuid::DescriptorType::CharacteristicUserDescription); + if (descriptor.isValid()) { + qDebug() << " Value before issuing read():" << descriptor.value(); + leCentralService->readDescriptor(descriptor); + } else { + qDebug() << "Descriptor was invalid"; + } +} + + +void BtLocalDevice::peripheralCharacteristicWrite() +{ + qDebug() << "######" << "LE peripheral write characteristic"; + if (!lePeripheralController || lePeripheralServices.isEmpty()) { 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)); + + auto characteristic = lePeripheralServices[0]->characteristic(QBluetoothUuid(leCharUuid1)); if (characteristic.isValid()) { + // Update value at the beginning and end so we can check whole data is sent in large writes + leCharacteristicValue[0] = ++leCharacteristicValueUpdate; + leCharacteristicValue[leCharacteristicSize - 1] = leCharacteristicValueUpdate; qDebug() << " Peripheral writes value:" << leCharacteristicValue; - lePeripheralService->writeCharacteristic(characteristic, leCharacteristicValue); + lePeripheralServices[0]->writeCharacteristic(characteristic, leCharacteristicValue); } else { qDebug() << "Characteristic was invalid"; } } -void BtLocalDevice::peripheralRead() +void BtLocalDevice::peripheralCharacteristicRead() { - qDebug() << "######" << "LE peripheral read"; - if (!lePeripheralController || !lePeripheralService) { + qDebug() << "######" << "LE peripheral read characteristic"; + if (!lePeripheralController || lePeripheralServices.isEmpty()) { qDebug() << "Peripheral or peripheral service does not exist"; return; } - auto characteristic = lePeripheralService->characteristic(QBluetoothUuid(leCharacteristicUuid)); + auto characteristic = lePeripheralServices[0]->characteristic(QBluetoothUuid(leCharUuid1)); if (characteristic.isValid()) qDebug() << " Value:" << characteristic.value(); else qDebug() << "Characteristic was invalid"; } +void BtLocalDevice::peripheralDescriptorWrite() +{ + qDebug() << "######" << "LE peripheral write descriptor"; + if (!lePeripheralController || lePeripheralServices.isEmpty()) { + qDebug() << "Peripheral or peripheral service does not exist"; + return; + } + auto descriptor = lePeripheralServices[0]->characteristic(QBluetoothUuid(leCharUuid1)) + .descriptor(QBluetoothUuid::DescriptorType::CharacteristicUserDescription); + + if (descriptor.isValid()) { + leDescriptorValue[0] = leDescriptorValueUpdate++; + qDebug() << " Peripheral writes value: " << leDescriptorValue; + lePeripheralServices[0]->writeDescriptor(descriptor, leDescriptorValue); + } else { + qDebug() << "Descriptor was invalid"; + } +} + +void BtLocalDevice::peripheralDescriptorRead() +{ + qDebug() << "######" << "LE peripheral read descriptor"; + if (!lePeripheralController || lePeripheralServices.isEmpty()) { + qDebug() << "Peripheral or peripheral service does not exist"; + return; + } + auto descriptor = lePeripheralServices[0]->characteristic(QBluetoothUuid(leCharUuid1)) + .descriptor(QBluetoothUuid::DescriptorType::CharacteristicUserDescription); + if (descriptor.isValid()) + qDebug() << " Value:" << descriptor.value(); + else + qDebug() << "Descriptor was invalid"; +} + void BtLocalDevice::startLeDeviceDiscovery() { qDebug() << "######" << "LE device discovery start for:" << leRemotePeriphreralDeviceName; @@ -1185,6 +1340,10 @@ void BtLocalDevice::centralDiscoverServiceDetails() [](const QLowEnergyCharacteristic&, const QByteArray& value){ qDebug() << "LE central service characteristic value changed" << value; }); + QObject::connect(leCentralService.get(), &QLowEnergyService::descriptorRead, + [](const QLowEnergyDescriptor&, const QByteArray& value){ + qDebug() << "LE central service descriptor value read" << value; + }); QObject::connect(leCentralService.get(), &QLowEnergyService::descriptorWritten, [this](const QLowEnergyDescriptor&, const QByteArray& value){ qDebug() << "LE central service descriptor value written" << value; @@ -1253,8 +1412,8 @@ void BtLocalDevice::dumpLeInfo() serviceDump(leCentralService.get()); qDebug() << "######" << "LE Peripheral-side service"; - if (lePeripheralService) - serviceDump(lePeripheralService.get()); + if (!lePeripheralServices.isEmpty()) + serviceDump(lePeripheralServices[0].get()); } void BtLocalDevice::centralSubscribeUnsubscribe() @@ -1264,7 +1423,7 @@ void BtLocalDevice::centralSubscribeUnsubscribe() qDebug() << "Service object does not exist"; return; } - auto characteristic = leCentralService->characteristic(QBluetoothUuid(leCharacteristicUuid)); + auto characteristic = leCentralService->characteristic(QBluetoothUuid(leCharUuid1)); if (!characteristic.isValid()) { qDebug() << "Characteristic is not valid"; return; @@ -1294,6 +1453,16 @@ void BtLocalDevice::centralDelete() emit leChanged(); } +void BtLocalDevice::centralDisconnect() +{ + qDebug() << "######" << "LE central disconnect"; + if (!leCentralController) { + qDebug() << "Create central first"; + return; + } + leCentralController->disconnectFromDevice(); +} + void BtLocalDevice::peripheralDelete() { qDebug() << "######" << "Delete peripheral" << lePeripheralController.get(); @@ -1301,6 +1470,16 @@ void BtLocalDevice::peripheralDelete() emit leChanged(); } +void BtLocalDevice::peripheralDisconnect() +{ + qDebug() << "######" << "LE peripheral disconnect"; + if (!lePeripheralController) { + qDebug() << "Create peripheral first"; + return; + } + lePeripheralController->disconnectFromDevice(); +} + bool BtLocalDevice::centralExists() const { return leCentralController.get(); @@ -1311,7 +1490,7 @@ bool BtLocalDevice::centralSubscribed() const if (!leCentralService) return false; - auto characteristic = leCentralService->characteristic(QBluetoothUuid(leCharacteristicUuid)); + auto characteristic = leCentralService->characteristic(QBluetoothUuid(leCharUuid1)); if (!characteristic.isValid()) return false; @@ -1378,10 +1557,10 @@ QByteArray BtLocalDevice::peripheralState() const QByteArray BtLocalDevice::peripheralServiceState() const { - if (!lePeripheralService) + if (lePeripheralServices.isEmpty()) return "(N/A)"_ba; - return serviceStateString[lePeripheralService->state()]; + return serviceStateString[lePeripheralServices[0]->state()]; } QByteArray BtLocalDevice::peripheralError() const @@ -1394,10 +1573,10 @@ QByteArray BtLocalDevice::peripheralError() const QByteArray BtLocalDevice::peripheralServiceError() const { - if (!lePeripheralService) + if (lePeripheralServices.isEmpty()) return "(N/A)"_ba; - return serviceErrorString[lePeripheralService->error()]; + return serviceErrorString[lePeripheralServices[0]->error()]; } bool BtLocalDevice::peripheralExists() const diff --git a/tests/bttestui/btlocaldevice.h b/tests/bttestui/btlocaldevice.h index 4c093bdb..9ac0e6ec 100644 --- a/tests/bttestui/btlocaldevice.h +++ b/tests/bttestui/btlocaldevice.h @@ -109,10 +109,13 @@ public slots: void centralConnect(); void centralStartServiceDiscovery(); void centralDiscoverServiceDetails(); - void centralWrite(); - void centralRead(); + void centralCharacteristicWrite(); + void centralCharacteristicRead(); + void centralDescriptorWrite(); + void centralDescriptorRead(); void centralSubscribeUnsubscribe(); void centralDelete(); + void centralDisconnect(); bool centralExists() const; bool centralSubscribed() const; QByteArray centralState() const; @@ -124,11 +127,15 @@ public slots: //QLowEnergyController peripheral void peripheralCreate(); + void peripheralAddServices(); void peripheralStartAdvertising(); void peripheralStopAdvertising(); - void peripheralWrite(); - void peripheralRead(); + void peripheralCharacteristicRead(); + void peripheralCharacteristicWrite(); + void peripheralDescriptorRead(); + void peripheralDescriptorWrite(); void peripheralDelete(); + void peripheralDisconnect(); bool peripheralExists() const; QByteArray peripheralState() const; QByteArray peripheralServiceState() const; @@ -154,10 +161,10 @@ private: std::unique_ptr<QLowEnergyController> leCentralController; std::unique_ptr<QLowEnergyController> lePeripheralController; - std::unique_ptr<QLowEnergyService> lePeripheralService; std::unique_ptr<QLowEnergyService> leCentralService; QLowEnergyAdvertisingData leAdvertisingData; - QLowEnergyServiceData leServiceData; + QList<QLowEnergyServiceData> lePeripheralServiceData; + QList<QSharedPointer<QLowEnergyService>> lePeripheralServices; QBluetoothDeviceInfo leRemotePeripheralDevice; QByteArray latestRSSI = "N/A"; }; diff --git a/tests/bttestui/main.qml b/tests/bttestui/main.qml index 38823199..4105c561 100644 --- a/tests/bttestui/main.qml +++ b/tests/bttestui/main.qml @@ -262,11 +262,19 @@ Flickable { } Button { buttonText: "CharacteristicRead" - onClicked: device.centralRead() + onClicked: device.centralCharacteristicRead() } Button { buttonText: "CharacteristicWrite" - onClicked: device.centralWrite() + onClicked: device.centralCharacteristicWrite() + } + Button { + buttonText: "DescriptorRead" + onClicked: device.centralDescriptorRead() + } + Button { + buttonText: "DescriptorWrite" + onClicked: device.centralDescriptorWrite() } Button { buttonText: "Sub/Unsubscribe" @@ -277,6 +285,10 @@ Flickable { onClicked: device.centralDelete() } Button { + buttonText: "Disconnect" + onClicked: device.centralDisconnect() + } + Button { buttonText: "ReadRSSI" onClicked: device.centralReadRSSI() } @@ -306,13 +318,15 @@ Flickable { } } Button { - id: peripheralBtn buttonText: "1 CreatePeripheral" onClicked: device.peripheralCreate() } Button { - id: advertiseBtn - buttonText: "2 StartAdvertise" + buttonText: "2 AddServices" + onClicked: device.peripheralAddServices() + } + Button { + buttonText: "3 StartAdvertise" onClicked: device.peripheralStartAdvertising() } Button { @@ -321,16 +335,28 @@ Flickable { } Button { buttonText: "CharacteristicRead" - onClicked: device.peripheralRead() + onClicked: device.peripheralCharacteristicRead() } Button { buttonText: "CharacteristicWrite" - onClicked: device.peripheralWrite() + onClicked: device.peripheralCharacteristicWrite() + } + Button { + buttonText: "DescriptorRead" + onClicked: device.peripheralDescriptorRead() + } + Button { + buttonText: "DescriptorWrite" + onClicked: device.peripheralDescriptorWrite() } Button { buttonText: "DeleteController" onClicked: device.peripheralDelete() } + Button { + buttonText: "Disconnect" + onClicked: device.peripheralDisconnect() + } } Column { spacing: 8 diff --git a/tests/manual/qlowenergycontroller/tst_qlowenergycontroller_device.cpp b/tests/manual/qlowenergycontroller/tst_qlowenergycontroller_device.cpp index 87dab2f1..50054a92 100644 --- a/tests/manual/qlowenergycontroller/tst_qlowenergycontroller_device.cpp +++ b/tests/manual/qlowenergycontroller/tst_qlowenergycontroller_device.cpp @@ -12,8 +12,10 @@ #include <QScopeGuard> #include <QBluetoothLocalDevice> -static const QLatin1String largeCharacteristicServiceUuid("1f85e37c-ac16-11eb-ae5c-93d3a763feed"); -static const QLatin1String largeCharacteristicCharUuid("40e4f68e-ac16-11eb-9956-cfe55a8c370c"); +static const QLatin1String largeAttributeServiceUuid("1f85e37c-ac16-11eb-ae5c-93d3a763feed"); +static const QLatin1String largeAttributeCharUuid("40e4f68e-ac16-11eb-9956-cfe55a8c370c"); +static const QLatin1String largeAttributeDescUuid("44e4f68e-ac16-11eb-9956-cfe55a8c370c"); +static constexpr qsizetype largeAttributeSize{508}; // Size for char and desc values static const QLatin1String platformIdentifierServiceUuid("4a92cb7f-5031-4a09-8304-3e89413f458d"); static const QLatin1String platformIdentifierCharUuid("6b0ecf7c-5f09-4c87-aaab-bb49d5d383aa"); @@ -89,6 +91,7 @@ private slots: void rssiRead(); #endif void readWriteLargeCharacteristic(); + void readWriteLargeDescriptor(); void readDuringServiceDiscovery(); void readNotificationAndIndicationProperty(); void testNotificationAndIndication(); @@ -420,6 +423,15 @@ void tst_qlowenergycontroller_device::checkconnectionCounter( void tst_qlowenergycontroller_device::readWriteLargeCharacteristic() { + // This tests reading and writing a large characteristic value + // + // Most modern platforms cope with up-to 512 bytes. One exception is Bluez DBus peripheral, + // which may reject a write from client when going above 508 due to the way it internally + // checks the payload size (tested with Bluez 5.64). + // + // This test can also be used for testing long (aka prepared) writes & reads by setting + // the MTU to for example 50 bytes. Asking a specific MTU is platform-specific and + // there is no Qt API for it QVERIFY(mController->services().isEmpty()); mController->discoverServices(); QTRY_COMPARE(mController->state(), QLowEnergyController::DiscoveredState); @@ -427,18 +439,17 @@ void tst_qlowenergycontroller_device::readWriteLargeCharacteristic() checkconnectionCounter(mController); QSharedPointer<QLowEnergyService> service( - mController->createServiceObject(QBluetoothUuid(largeCharacteristicServiceUuid))); + mController->createServiceObject(QBluetoothUuid(largeAttributeServiceUuid))); QVERIFY(service != nullptr); service->discoverDetails(QLowEnergyService::SkipValueDiscovery); QTRY_COMPARE(service->state(), QLowEnergyService::ServiceState::RemoteServiceDiscovered); QSignalSpy readSpy(service.get(), &QLowEnergyService::characteristicRead); QSignalSpy writtenSpy(service.get(), &QLowEnergyService::characteristicWritten); - QCOMPARE(readSpy.size(), 0); - QCOMPARE(writtenSpy.size(), 0); - // The service discovery skipped the values => check that the default value is all zeroes - auto characteristic = service->characteristic(QBluetoothUuid(largeCharacteristicCharUuid)); + // The service discovery skipped the values => check that the default values are all zeroes + auto characteristic = service->characteristic(QBluetoothUuid(largeAttributeCharUuid)); + QVERIFY(characteristic.isValid()); QByteArray testArray(0); qDebug() << "Initial large characteristic value:" << characteristic.value(); QCOMPARE(characteristic.value(), testArray); @@ -447,12 +458,12 @@ void tst_qlowenergycontroller_device::readWriteLargeCharacteristic() service->readCharacteristic(characteristic); QTRY_COMPARE(readSpy.size(), 1); qDebug() << "Large characteristic value after read:" << characteristic.value(); - testArray = QByteArray(512, 0); + testArray = QByteArray(largeAttributeSize, 0); testArray[0] = 0x0b; QCOMPARE(characteristic.value(), testArray); // Write a new value to characteristic and read it back - for (int i = 0; i < 512; ++i) { + for (int i = 0; i < largeAttributeSize; ++i) { testArray[i] = i % 5; } service->writeCharacteristic(characteristic, testArray); @@ -465,6 +476,54 @@ void tst_qlowenergycontroller_device::readWriteLargeCharacteristic() QCOMPARE(characteristic.value(), testArray); } +void tst_qlowenergycontroller_device::readWriteLargeDescriptor() +{ + // This tests reading and writing a large descriptor value + QVERIFY(mController->services().isEmpty()); + mController->discoverServices(); + QTRY_COMPARE(mController->state(), QLowEnergyController::DiscoveredState); + + checkconnectionCounter(mController); + + QSharedPointer<QLowEnergyService> service( + mController->createServiceObject(QBluetoothUuid(largeAttributeServiceUuid))); + QVERIFY(service != nullptr); + service->discoverDetails(QLowEnergyService::FullDiscovery); + QTRY_COMPARE(service->state(), QLowEnergyService::ServiceState::RemoteServiceDiscovered); + + QSignalSpy readSpy(service.get(), &QLowEnergyService::descriptorRead); + QSignalSpy writtenSpy(service.get(), &QLowEnergyService::descriptorWritten); + + auto characteristic = service->characteristic(QBluetoothUuid(largeAttributeCharUuid)); + QVERIFY(characteristic.isValid()); + auto descriptor = characteristic.descriptor(QBluetoothUuid(largeAttributeDescUuid)); + QVERIFY(descriptor.isValid()); + + QByteArray testArray = QByteArray(largeAttributeSize, 0); + testArray[0] = 0xdd; + + // Read descriptor value and verify it is what the server set (0xdd 0x00 ..) + QVERIFY(readSpy.isEmpty()); + QVERIFY(writtenSpy.isEmpty()); + service->readDescriptor(descriptor); + QTRY_COMPARE(readSpy.size(), 1); + qDebug() << "Large descriptor value after read:" << descriptor.value(); + QCOMPARE(descriptor.value(), testArray); + + // Write a new value to descriptor and read it back + for (int i = 0; i < largeAttributeSize; ++i) { + testArray[i] = i % 5; + } + service->writeDescriptor(descriptor, testArray); + QCOMPARE(service->error(), QLowEnergyService::ServiceError::NoError); + QTRY_COMPARE(writtenSpy.size(), 1); + + service->readDescriptor(descriptor); + QTRY_COMPARE(readSpy.size(), 2); + qDebug() << "Large descriptor value after write/read:" << descriptor.value(); + QCOMPARE(descriptor.value(), testArray); +} + void tst_qlowenergycontroller_device::readDuringServiceDiscovery() { QVERIFY(mController->services().isEmpty()); @@ -474,7 +533,7 @@ void tst_qlowenergycontroller_device::readDuringServiceDiscovery() checkconnectionCounter(mController); QSharedPointer<QLowEnergyService> service( - mController->createServiceObject(QBluetoothUuid(largeCharacteristicServiceUuid))); + mController->createServiceObject(QBluetoothUuid(largeAttributeServiceUuid))); QVERIFY(service != nullptr); service->discoverDetails(QLowEnergyService::FullDiscovery); QTRY_COMPARE(service->state(), QLowEnergyService::ServiceState::RemoteServiceDiscovered); @@ -485,11 +544,11 @@ void tst_qlowenergycontroller_device::readDuringServiceDiscovery() QCOMPARE(writtenSpy.size(), 0); // Value that is initially set on the characteristic at the server-side (0x0b 0x00 ..) - QByteArray testArray(512, 0); + QByteArray testArray(largeAttributeSize, 0); testArray[0] = 0x0b; // We did a full service discovery and should have an initial value to compare - auto characteristic = service->characteristic(QBluetoothUuid(largeCharacteristicCharUuid)); + auto characteristic = service->characteristic(QBluetoothUuid(largeAttributeCharUuid)); QByteArray valueFromServiceDiscovery = characteristic.value(); qDebug() << "Large characteristic value from service discovery:" << valueFromServiceDiscovery; // On darwin the server does not restart (disconnect) in-between the case runs @@ -504,7 +563,7 @@ void tst_qlowenergycontroller_device::readDuringServiceDiscovery() QCOMPARE(characteristic.value(), valueFromServiceDiscovery); // Write a new value to the characteristic and read it back - for (int i = 0; i < 512; ++i) { + for (int i = 0; i < largeAttributeSize; ++i) { testArray[i] = i % 5; } service->writeCharacteristic(characteristic, testArray); |