From c4f5a247cccda4bad46aeff530364f7e4da2df57 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Tue, 8 Dec 2015 11:36:58 +0100 Subject: Bluetooth LE: Implement GATT server write support. Write Request, Write Command and Execute Write Request are fully implemented now. Signed Write support is still missing. Notifications and Indications are sent. The server side gets informed via the respective signals when a client writes a characteristic or descriptor. Change-Id: Icba6a0270f6e1c4c3ed2ba61b55c1a5fbb69752b Reviewed-by: Alex Blasche --- .../server/qlowenergycontroller-gattserver.cpp | 72 ++++++++++++- .../test/tst_qlowenergycontroller-gattserver.cpp | 115 +++++++++++++++++++-- 2 files changed, 176 insertions(+), 11 deletions(-) (limited to 'tests') diff --git a/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp index b1fc7256..97adf9db 100644 --- a/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp +++ b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp @@ -48,6 +48,9 @@ static QByteArray deviceName() { return "Qt GATT server"; } static QScopedPointer leController; typedef QSharedPointer ServicePtr; static QHash services; +static int descriptorWriteCount = 0; +static int disconnectCount = 0; +static QBluetoothAddress remoteDevice; void addService(const QLowEnergyServiceData &serviceData) { @@ -64,7 +67,7 @@ void addRunningSpeedService() QLowEnergyDescriptorData desc; desc.setUuid(QBluetoothUuid::ClientCharacteristicConfiguration); - desc.setValue(QByteArray(1, 0)); // Default: No indication, no notification. + desc.setValue(QByteArray(2, 0)); // Default: No indication, no notification. QLowEnergyCharacteristicData charData; charData.setUuid(QBluetoothUuid::RSCMeasurement); charData.addDescriptor(desc); @@ -111,7 +114,7 @@ void addGenericAccessService() void addCustomService() { QLowEnergyServiceData serviceData; - serviceData.setUuid(QBluetoothUuid(quint16(0x2000))); // Made up. + serviceData.setUuid(QBluetoothUuid(quint16(0x2000))); serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary); QLowEnergyCharacteristicData charData; @@ -120,12 +123,24 @@ void addCustomService() charData.setValue(QByteArray(1024, 'x')); // Long value to test "Read Blob". serviceData.addCharacteristic(charData); - charData.setUuid(QBluetoothUuid(quint16(0x5001))); // Made up. + charData.setUuid(QBluetoothUuid(quint16(0x5001))); charData.setProperties(QLowEnergyCharacteristic::Read); charData.setReadConstraints(QBluetooth::AttAuthorizationRequired); // To test read failure. serviceData.addCharacteristic(charData); charData.setValue("something"); + charData.setUuid(QBluetoothUuid(quint16(0x5002))); + charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Indicate); + charData.setReadConstraints(QBluetooth::AttAccessConstraints()); + const QLowEnergyDescriptorData desc(QBluetoothUuid::ClientCharacteristicConfiguration, + QByteArray(2, 0)); + charData.addDescriptor(desc); + serviceData.addCharacteristic(charData); + + charData.setUuid(QBluetoothUuid(quint16(0x5003))); + charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify); + serviceData.addCharacteristic(charData); + addService(serviceData); } @@ -150,8 +165,55 @@ int main(int argc, char *argv[]) addCustomService(); startAdvertising(); - // TODO: Change characteristics, client checks that it gets indication/notification - // TODO: Where to test that we get the characteristicChanged signal for characteristics that the client writes? + const ServicePtr customService = services.value(QBluetoothUuid(quint16(0x2000))); + Q_ASSERT(customService); + + const auto stateChangedHandler = [customService]() { + switch (leController->state()) { + case QLowEnergyController::ConnectedState: + remoteDevice = leController->remoteAddress(); + break; + case QLowEnergyController::UnconnectedState: { + if (++disconnectCount == 2) { + qApp->quit(); + break; + } + Q_ASSERT(disconnectCount == 1); + const QLowEnergyCharacteristic indicatableChar + = customService->characteristic(QBluetoothUuid(quint16(0x5002))); + Q_ASSERT(indicatableChar.isValid()); + customService->writeCharacteristic(indicatableChar, "indicated2"); + Q_ASSERT(indicatableChar.value() == "indicated2"); + const QLowEnergyCharacteristic notifiableChar + = customService->characteristic(QBluetoothUuid(quint16(0x5003))); + Q_ASSERT(notifiableChar.isValid()); + customService->writeCharacteristic(notifiableChar, "notified2"); + Q_ASSERT(notifiableChar.value() == "notified2"); + startAdvertising(); + break; + } + default: + break; + } + }; + + QObject::connect(leController.data(), &QLowEnergyController::stateChanged, stateChangedHandler); + const auto descriptorWriteHandler = [customService]() { + if (++descriptorWriteCount != 2) + return; + const QLowEnergyCharacteristic indicatableChar + = customService->characteristic(QBluetoothUuid(quint16(0x5002))); + Q_ASSERT(indicatableChar.isValid()); + customService->writeCharacteristic(indicatableChar, "indicated"); + Q_ASSERT(indicatableChar.value() == "indicated"); + const QLowEnergyCharacteristic notifiableChar + = customService->characteristic(QBluetoothUuid(quint16(0x5003))); + Q_ASSERT(notifiableChar.isValid()); + customService->writeCharacteristic(notifiableChar, "notified"); + Q_ASSERT(notifiableChar.value() == "notified"); + }; + QObject::connect(customService.data(), &QLowEnergyService::descriptorWritten, + descriptorWriteHandler); return app.exec(); } diff --git a/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp index 25098273..c1701217 100644 --- a/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp +++ b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -63,7 +64,7 @@ private slots: // Interaction with actual GATT server goes here. Order is relevant. void advertisedData(); - void initialServices(); + void serverCommunication(); private: QBluetoothAddress m_serverAddress; @@ -171,8 +172,15 @@ void TestQLowEnergyControllerGattServer::advertisedData() QVERIFY(m_serverInfo.serviceUuids().contains(QBluetoothUuid(quint16(0x2000)))); } -void TestQLowEnergyControllerGattServer::initialServices() +// TODO: Why on earth is this not in the library??? +Q_DECLARE_METATYPE(QLowEnergyCharacteristic) +Q_DECLARE_METATYPE(QLowEnergyDescriptor) + +void TestQLowEnergyControllerGattServer::serverCommunication() { + qRegisterMetaType(); + qRegisterMetaType(); + if (m_serverAddress.isNull()) QSKIP("No server address provided"); m_leController.reset(QLowEnergyController::createCentral(m_serverInfo)); @@ -235,7 +243,7 @@ void TestQLowEnergyControllerGattServer::initialServices() const QLowEnergyDescriptor clientConfigDesc = measurementChar.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); QVERIFY(clientConfigDesc.isValid()); - QCOMPARE(clientConfigDesc.value(), QByteArray(1, 0)); + QCOMPARE(clientConfigDesc.value(), QByteArray(2, 0)); QCOMPARE(measurementChar.properties(), QLowEnergyCharacteristic::Notify); QCOMPARE(measurementChar.value(), QByteArray()); // Empty because Read property not set QLowEnergyCharacteristic featureChar @@ -247,16 +255,16 @@ void TestQLowEnergyControllerGattServer::initialServices() featureValue[0] = 1 << 2; QCOMPARE(featureChar.value(), featureValue); - const QScopedPointer customService( + QScopedPointer customService( m_leController->createServiceObject(QBluetoothUuid(quint16(0x2000)))); QVERIFY(!customService.isNull()); customService->discoverDetails(); while (customService->state() != QLowEnergyService::ServiceDiscovered) { spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::stateChanged)); - QVERIFY(spy->wait(3000)); + QVERIFY(spy->wait(5000)); } QCOMPARE(customService->includedServices().count(), 0); - QCOMPARE(customService->characteristics().count(), 2); + QCOMPARE(customService->characteristics().count(), 4); QLowEnergyCharacteristic customChar = customService->characteristic(QBluetoothUuid(quint16(0x5000))); QVERIFY(customChar.isValid()); @@ -269,11 +277,99 @@ void TestQLowEnergyControllerGattServer::initialServices() QCOMPARE(customChar2.descriptors().count(), 0); QCOMPARE(customChar2.value(), QByteArray()); // Was not readable due to authorization requirement. + QLowEnergyCharacteristic customChar3 + = customService->characteristic(QBluetoothUuid(quint16(0x5002))); + QVERIFY(customChar3.isValid()); + QCOMPARE(customChar3.properties(), + QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Indicate); + QCOMPARE(customChar3.descriptors().count(), 1); + QLowEnergyDescriptor cc3ClientConfig + = customChar3.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + QVERIFY(cc3ClientConfig.isValid()); + + QLowEnergyCharacteristic customChar4 + = customService->characteristic(QBluetoothUuid(quint16(0x5003))); + QVERIFY(customChar4.isValid()); + QCOMPARE(customChar4.properties(), + QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify); + QCOMPARE(customChar4.descriptors().count(), 1); + QLowEnergyDescriptor cc4ClientConfig + = customChar4.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + QVERIFY(cc4ClientConfig.isValid()); + customService->writeCharacteristic(customChar, "whatever"); spy.reset(new QSignalSpy(customService.data(), static_cast(&QLowEnergyService::error))); QVERIFY(spy->wait(3000)); QCOMPARE(customService->error(), QLowEnergyService::CharacteristicWriteError); + + QByteArray indicateValue(2, 0); + indicateValue[0] = 2; + customService->writeDescriptor(cc3ClientConfig, indicateValue); + spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::descriptorWritten)); + QVERIFY(spy->wait(3000)); + + QByteArray notifyValue(2, 0); + notifyValue[0] = 1; + customService->writeDescriptor(cc4ClientConfig, notifyValue); + spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::descriptorWritten)); + QVERIFY(spy->wait(3000)); + + // Server now changes the characteristic values. + + spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::characteristicChanged)); + QVERIFY(spy->wait(3000)); + if (spy->count() == 1) + QVERIFY(spy->wait(3000)); + QCOMPARE(customChar3.value().constData(), "indicated"); + QCOMPARE(customChar4.value().constData(), "notified"); + + const bool isBonded = QBluetoothLocalDevice().pairingStatus(m_serverAddress) + != QBluetoothLocalDevice::Unpaired; + m_leController->disconnectFromDevice(); + + if (m_leController->state() != QLowEnergyController::UnconnectedState) { + spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::stateChanged)); + QVERIFY(spy->wait(3000)); + } + QCOMPARE(m_leController->state(), QLowEnergyController::UnconnectedState); + + // Server now changes the characteristic values again while we're offline. + // Note: We cannot test indications and notifications for this case, as the client does + // not cache the old information and thus does not yet know the characteristics + // at the time the notification/indication is received. + + QTest::qWait(3000); + m_leController->connectToDevice(); + spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::connected)); + QVERIFY(spy->wait(30000)); + m_leController->discoverServices(); + spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::discoveryFinished)); + QVERIFY(spy->wait(30000)); + customService.reset(m_leController->createServiceObject(QBluetoothUuid(quint16(0x2000)))); + QVERIFY(!customService.isNull()); + customService->discoverDetails(); + while (customService->state() != QLowEnergyService::ServiceDiscovered) { + spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::stateChanged)); + QVERIFY(spy->wait(5000)); + } + customChar3 = customService->characteristic(QBluetoothUuid(quint16(0x5002))); + QVERIFY(customChar3.isValid()); + QCOMPARE(customChar3.value().constData(), "indicated2"); + customChar4 = customService->characteristic(QBluetoothUuid(quint16(0x5003))); + QVERIFY(customChar4.isValid()); + QCOMPARE(customChar4.value().constData(), "notified2"); + cc3ClientConfig = customChar3.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + QVERIFY(cc3ClientConfig.isValid()); + cc4ClientConfig = customChar4.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + QVERIFY(cc4ClientConfig.isValid()); + if (isBonded) { + QCOMPARE(cc3ClientConfig.value(), indicateValue); + QCOMPARE(cc4ClientConfig.value(), notifyValue); + } else { + QCOMPARE(cc3ClientConfig.value(), QByteArray(2, 0)); + QCOMPARE(cc4ClientConfig.value(), QByteArray(2, 0)); + } } void TestQLowEnergyControllerGattServer::controllerType() @@ -320,6 +416,13 @@ void TestQLowEnergyControllerGattServer::serviceData() charData.setValue("value"); QCOMPARE(charData.value().constData(), "value"); + charData.setValueLength(4, 7); + QCOMPARE(charData.minimumValueLength(), 4); + QCOMPARE(charData.maximumValueLength(), 7); + charData.setValueLength(5, 2); + QCOMPARE(charData.minimumValueLength(), 5); + QCOMPARE(charData.maximumValueLength(), 5); + const QLowEnergyCharacteristic::PropertyTypes props = QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::WriteSigned; charData.setProperties(props); -- cgit v1.2.3