diff options
author | Alex Blasche <alexander.blasche@digia.com> | 2014-08-27 10:57:08 +0200 |
---|---|---|
committer | Alex Blasche <alexander.blasche@digia.com> | 2014-09-02 20:18:21 +0200 |
commit | 4ab9c732a466dc793e5ec162a928d4350a29281c (patch) | |
tree | b5fb9c1e074fb8228936d34894ab61dd452e003f /tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp | |
parent | bb4de7e992fc673884db553977b2756a165278b5 (diff) |
Add support for BTLE write command (BlueZ/Linux)
So far, we only supported write requests which reply with write
responses.
Change-Id: Ibdad36dcf18dec23260f003911b9361cc4ab1e3d
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
Diffstat (limited to 'tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp')
-rw-r--r-- | tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp | 297 |
1 files changed, 282 insertions, 15 deletions
diff --git a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp index 02f225ee..b282e943 100644 --- a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp +++ b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp @@ -68,9 +68,18 @@ private slots: void tst_defaultBehavior(); void tst_writeCharacteristic(); void tst_writeDescriptor(); + void tst_writeDescriptorNoResponse(); + private: void verifyServiceProperties(const QLowEnergyService *info); + QString checkIodResults(const QLowEnergyCharacteristic &original, + const QLowEnergyCharacteristic &first, + const QLowEnergyCharacteristic &second, + const QByteArray &writeValue, + const QByteArray &firstValue, + const QByteArray & secondValue, + bool* success = 0); QBluetoothDeviceDiscoveryAgent *devAgent; QBluetoothAddress remoteDevice; @@ -1324,7 +1333,9 @@ void tst_QLowEnergyController::verifyServiceProperties( QCOMPARE(chars[0].handle(), QLowEnergyHandle(0x77)); QCOMPARE(chars[0].properties(), (QLowEnergyCharacteristic::Notify|QLowEnergyCharacteristic::Read)); - QCOMPARE(chars[0].value(), QByteArray::fromHex("000000000000")); + // the connection control parameter change from platform to platform + // better not test them here + //QCOMPARE(chars[0].value(), QByteArray::fromHex("000000000000")); QVERIFY(chars[0].isValid()); QVERIFY(info->contains(chars[0])); @@ -1734,10 +1745,10 @@ void tst_QLowEnergyController::tst_writeDescriptor() } QCOMPARE(notification.value(), QByteArray::fromHex("0000")); - service->contains(notification); - service->contains(tempData); + QVERIFY(service->contains(notification)); + QVERIFY(service->contains(tempData)); if (tempConfig.isValid()) { - service->contains(tempConfig); + QVERIFY(service->contains(tempConfig)); QCOMPARE(tempConfig.value(), QByteArray::fromHex("00")); } @@ -1746,16 +1757,23 @@ void tst_QLowEnergyController::tst_writeDescriptor() SIGNAL(descriptorChanged(QLowEnergyDescriptor,QByteArray))); QSignalSpy charChangedSpy(service, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray))); - service->writeDescriptor(notification, QByteArray::fromHex("0100")); - // verify - QTRY_VERIFY_WITH_TIMEOUT(!descChangedSpy.isEmpty(), 3000); - QCOMPARE(notification.value(), QByteArray::fromHex("0100")); - QList<QVariant> firstSignalData = descChangedSpy.first(); - QLowEnergyDescriptor signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>(); - QByteArray signalValue = firstSignalData[1].toByteArray(); - QCOMPARE(signalValue, QByteArray::fromHex("0100")); - QVERIFY(notification == signalDesc); - descChangedSpy.clear(); + + QLowEnergyDescriptor signalDesc; + QList<QVariant> firstSignalData; + QByteArray signalValue; + if (notification.value() != QByteArray::fromHex("0100")) { + // enable notifications if not already done + service->writeDescriptor(notification, QByteArray::fromHex("0100")); + + QTRY_VERIFY_WITH_TIMEOUT(!descChangedSpy.isEmpty(), 3000); + QCOMPARE(notification.value(), QByteArray::fromHex("0100")); + firstSignalData = descChangedSpy.first(); + signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>(); + signalValue = firstSignalData[1].toByteArray(); + QCOMPARE(signalValue, QByteArray::fromHex("0100")); + QVERIFY(notification == signalDesc); + descChangedSpy.clear(); + } // 4. Test reception of notifications // activate the temperature sensor if available @@ -1774,7 +1792,6 @@ void tst_QLowEnergyController::tst_writeDescriptor() if (i == 0) { QCOMPARE(tempConfig, ch); } else { - qDebug() << "Temp update: " << hex << ch.handle() << val.toHex(); QCOMPARE(tempData, ch); } } @@ -1826,6 +1843,256 @@ void tst_QLowEnergyController::tst_writeDescriptor() delete service; } + +// this logic can be simplified but requires API change +// TODO find way to distinguish characteristicChanged() due to notification and write request +QString tst_QLowEnergyController::checkIodResults( + const QLowEnergyCharacteristic &original, + const QLowEnergyCharacteristic &first, + const QLowEnergyCharacteristic &second, + const QByteArray &writeValue, + const QByteArray &firstValue, + const QByteArray &secondValue, bool *success) +{ + + if (original == first && first == second) { + //verify write confirmation + if ((firstValue.size() == 1 && (firstValue == writeValue)) + || (secondValue.size() == 1 && (secondValue == writeValue))) { + *success = true; + } else { + *success = false; + return QString("Could not find write request command confirmation"); + } + + // image ident info is 8 bytes long + if ((firstValue.size() == 8) || secondValue.size() == 8) { + *success = true; + } else { + *success = false; + return QString("Could not identify image ident info"); + } + return QString(); + } else if (first != second) { + // If the image doesn't exist, the notification doesn't come. + // In such cases the OAD Image block seems to send a notification + //expect at least write confirmation + if (firstValue.size() == 1 && (firstValue == writeValue)) { + *success = true; + return QString("Two chars don't match. Unknown char: %1 (%2)"). + arg(second.handle()).arg(QString(secondValue.toHex())); + } else if (secondValue.size() == 1 && (secondValue == writeValue)) { + *success = true; + return QString("Two chars don't match. Unknown char: %1(%2)"). + arg(first.handle()).arg(QString(firstValue.toHex())); + } else { + *success = false; + return QString("Could not find write request command confirmation"); + } + } + + return QString("Unexpected condition"); +} + + +/* + Tests write without responses. We utilize the Over-The-Air image update + service of the SensorTag. + */ +void tst_QLowEnergyController::tst_writeDescriptorNoResponse() +{ + QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices(); + if (localAdapters.isEmpty() || remoteDevice.isNull()) + QSKIP("No local Bluetooth or remote BTLE device found. Skipping test."); + + // quick setup - more elaborate test is done by connect() + QLowEnergyController control(remoteDevice); + QCOMPARE(control.error(), QLowEnergyController::NoError); + + control.connectToDevice(); + { + QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState, + 30000); + } + + if (control.state() == QLowEnergyController::ConnectingState + || control.error() != QLowEnergyController::NoError) { + // default BTLE backend forever hangs in ConnectingState + QSKIP("Cannot connect to remote device"); + } + + QCOMPARE(control.state(), QLowEnergyController::ConnectedState); + QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished())); + QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState))); + control.discoverServices(); + QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 10000); + QCOMPARE(stateSpy.count(), 2); + QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(), + QLowEnergyController::DiscoveringState); + QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(), + QLowEnergyController::DiscoveredState); + + // The Over-The-Air update service uuid + const QBluetoothUuid testService(QString("f000ffc0-0451-4000-b000-000000000000")); + QList<QBluetoothUuid> uuids = control.services(); + QVERIFY(uuids.contains(testService)); + + QLowEnergyService *service = control.createServiceObject(testService, this); + QVERIFY(service); + service->discoverDetails(); + QTRY_VERIFY_WITH_TIMEOUT( + service->state() == QLowEnergyService::ServiceDiscovered, 30000); + + // 1. Get "Image Identity" characteristic + const QLowEnergyCharacteristic imageIdentityChar = service->characteristic( + QBluetoothUuid(QString("f000ffc1-0451-4000-b000-000000000000"))); + QVERIFY(imageIdentityChar.isValid()); + QVERIFY(imageIdentityChar.properties() & QLowEnergyCharacteristic::Write); + QVERIFY(imageIdentityChar.properties() & QLowEnergyCharacteristic::WriteNoResponse); + + // 2. Get "Image Identity" notification descriptor + const QLowEnergyDescriptor notification = imageIdentityChar.descriptor( + QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)); + + if (!notification.isValid() || !imageIdentityChar.isValid()) { + delete service; + control.disconnectFromDevice(); + QSKIP("Cannot find OAD char/notification"); + } + + // 3. Enable notifications + QSignalSpy descChangedSpy(service, + SIGNAL(descriptorChanged(QLowEnergyDescriptor,QByteArray))); + QSignalSpy charChangedSpy(service, + SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray))); + if (notification.value() != QByteArray::fromHex("0100")) { + service->writeDescriptor(notification, QByteArray::fromHex("0100")); + QTRY_VERIFY_WITH_TIMEOUT(!descChangedSpy.isEmpty(), 3000); + QCOMPARE(notification.value(), QByteArray::fromHex("0100")); + QList<QVariant> firstSignalData = descChangedSpy.first(); + QLowEnergyDescriptor signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>(); + QByteArray signalValue = firstSignalData[1].toByteArray(); + QCOMPARE(signalValue, QByteArray::fromHex("0100")); + QVERIFY(notification == signalDesc); + descChangedSpy.clear(); + } + + // 4. Trigger image identity announcement (using traditional write) + + QByteArray imageAValue, imageBValue; + QList<QVariant> entry; + bool foundOneImage = false; + + // Image A + // Write triggers a notification and write confirmation + service->writeCharacteristic(imageIdentityChar, QByteArray::fromHex("0")); + QTest::qWait(1000); + QTRY_VERIFY_WITH_TIMEOUT(charChangedSpy.count() == 2, 10000); + + entry = charChangedSpy[0]; + QLowEnergyCharacteristic first = entry[0].value<QLowEnergyCharacteristic>(); + QByteArray val1 = entry[1].toByteArray(); + + entry = charChangedSpy[1]; + QLowEnergyCharacteristic second = entry[0].value<QLowEnergyCharacteristic>(); + QByteArray val2 = entry[1].toByteArray(); + + // This is very SensorTag specific logic. + // We don't know which of the two signals above is the write confirmation + // and which is the notification. If the image is not set the current firmware + // does not even send a notification for cimageIdentity + // checkIodResults() confirms the various cases + + bool isSuccess = false; + QString errorString = checkIodResults(imageIdentityChar, first, second, + QByteArray::fromHex("0"), + val1, val2, &isSuccess); + QVERIFY2(isSuccess, errorString.toLatin1().constData()); + if (!errorString.isEmpty()) { + // success but still some error hint provided - print it out. + qWarning() << "Warn1:" << errorString; + } else { + foundOneImage = true; + } + + charChangedSpy.clear(); + + // Image B + service->writeCharacteristic(imageIdentityChar, QByteArray::fromHex("1")); + QTest::qWait(1000); + QTRY_VERIFY_WITH_TIMEOUT(charChangedSpy.count() == 2, 10000); + + entry = charChangedSpy[0]; + first = entry[0].value<QLowEnergyCharacteristic>(); + val1 = entry[1].toByteArray(); + + entry = charChangedSpy[1]; + second = entry[0].value<QLowEnergyCharacteristic>(); + val2 = entry[1].toByteArray(); + + errorString = checkIodResults(imageIdentityChar, first, second, + QByteArray::fromHex("1"), + val1, val2, &isSuccess); + QVERIFY2(isSuccess, errorString.toLatin1().constData()); + if (!errorString.isEmpty()) { + // success but still some error hint provided - print it out. + qWarning() << "Warn2:" << errorString; + } else { + foundOneImage = true; + } + + QVERIFY2(foundOneImage, "The SensorTag doesn't have a valid image? (1)"); + charChangedSpy.clear(); + + // 5. Trigger image identity announcement (without response) + foundOneImage = false; + + // Image A + service->writeCharacteristic(imageIdentityChar, + QByteArray::fromHex("0"), + QLowEnergyService::WriteWithoutResponse); + + // we only expect one signal (the notification but not the write confirmation) + // Wait at least a second for a potential second signals + QTest::qWait(1000); + QTRY_VERIFY_WITH_TIMEOUT(charChangedSpy.count() == 1, 10000); + + entry = charChangedSpy[0]; + first = entry[0].value<QLowEnergyCharacteristic>(); + val1 = entry[1].toByteArray(); + + if (val1.size() == 8) + foundOneImage = true; + + qDebug() << "IOD write command image A:" << hex << first.handle() << val1.toHex(); + charChangedSpy.clear(); + + // Image B + service->writeCharacteristic(imageIdentityChar, + QByteArray::fromHex("1"), + QLowEnergyService::WriteWithoutResponse); + + // we only expect one signal (the notification but not the write confirmation) + // Wait at least a second for a potential second signals + QTest::qWait(1000); + QTRY_VERIFY_WITH_TIMEOUT(charChangedSpy.count() == 1, 10000); + + entry = charChangedSpy[0]; + first = entry[0].value<QLowEnergyCharacteristic>(); + val1 = entry[1].toByteArray(); + + if (val1.size() == 8) + foundOneImage = true; + + QVERIFY2(foundOneImage, "The SensorTag doesn't have a valid image? (2)"); + + qDebug() << "IOD write command image B:" << hex << first.handle() << val1.toHex(); + + delete service; + control.disconnectFromDevice(); + +} + QTEST_MAIN(tst_QLowEnergyController) #include "tst_qlowenergycontroller.moc" |