From 0b475c565843e6c0901558d6342a02722d57acbc Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Wed, 21 Jun 2017 15:45:26 +0200 Subject: Provide a way to define GAP/GATT services for central roles Some devices require the Generic Access (GAP) and Generic Attribute (GATT) services to be defined. Otherwise they refuse a proper interaction. This is done implicitly by Android and BlueZ. The reason why BlueZ does not do this for QtBluetooth is because QtBluetooth sets up its own GATT infrastructure. Normally a QLEController in central role cannot do that via public API as the QLEController::addService() function blocks on peripheral use cases. This patch sets the profiles up. In the future the feature really requires a better form of API (beyond the above env variable) or should be enabled by default but since we need this earlier than Qt 5.10 a more subtle approach was chosen. For now the feature can only be enabled if the QT_DEFAULT_CENTRAL_SERVICES was set. Another limitation is that the characteristics of the added services are completely static. Task-number: QTBUG-61554 Change-Id: Id03bddb2e54cc4f0869838e13ddf281311ad3a26 Reviewed-by: Christian Kandeler Reviewed-by: Alex Blasche --- src/bluetooth/qlowenergycontroller.cpp | 37 +++++++++------ src/bluetooth/qlowenergycontroller_bluez.cpp | 68 ++++++++++++++++++++++++++++ src/bluetooth/qlowenergycontroller_p.h | 3 ++ 3 files changed, 95 insertions(+), 13 deletions(-) diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp index 2337268d..053ca980 100644 --- a/src/bluetooth/qlowenergycontroller.cpp +++ b/src/bluetooth/qlowenergycontroller.cpp @@ -931,13 +931,24 @@ QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData return nullptr; } + Q_D(QLowEnergyController); + QLowEnergyService *newService = d->addServiceHelper(service); + if (newService) + newService->setParent(parent); + + return newService; +} + +QLowEnergyService *QLowEnergyControllerPrivate::addServiceHelper( + const QLowEnergyServiceData &service) +{ // Spec says services "should" be grouped by uuid length (16-bit first, then 128-bit). // Since this is not mandatory, we ignore it here and let the caller take responsibility // for it. const auto servicePrivate = QSharedPointer::create(); servicePrivate->state = QLowEnergyService::LocalService; - servicePrivate->setController(d_ptr); + servicePrivate->setController(this); servicePrivate->uuid = service.uuid(); servicePrivate->type = service.type() == QLowEnergyServiceData::ServiceTypePrimary ? QLowEnergyService::PrimaryService : QLowEnergyService::IncludedService; @@ -947,13 +958,13 @@ QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData } // Spec v4.2, Vol 3, Part G, Section 3. - const QLowEnergyHandle oldLastHandle = d_ptr->lastLocalHandle; - servicePrivate->startHandle = ++d_ptr->lastLocalHandle; // Service declaration. - d_ptr->lastLocalHandle += servicePrivate->includedServices.count(); // Include declarations. + const QLowEnergyHandle oldLastHandle = this->lastLocalHandle; + servicePrivate->startHandle = ++this->lastLocalHandle; // Service declaration. + this->lastLocalHandle += servicePrivate->includedServices.count(); // Include declarations. foreach (const QLowEnergyCharacteristicData &cd, service.characteristics()) { - const QLowEnergyHandle declHandle = ++d_ptr->lastLocalHandle; + const QLowEnergyHandle declHandle = ++this->lastLocalHandle; QLowEnergyServicePrivate::CharData charData; - charData.valueHandle = ++d_ptr->lastLocalHandle; + charData.valueHandle = ++this->lastLocalHandle; charData.uuid = cd.uuid(); charData.properties = cd.properties(); charData.value = cd.value(); @@ -961,21 +972,21 @@ QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData QLowEnergyServicePrivate::DescData descData; descData.uuid = dd.uuid(); descData.value = dd.value(); - charData.descriptorList.insert(++d_ptr->lastLocalHandle, descData); + charData.descriptorList.insert(++this->lastLocalHandle, descData); } servicePrivate->characteristicList.insert(declHandle, charData); } - servicePrivate->endHandle = d_ptr->lastLocalHandle; - const bool handleOverflow = d_ptr->lastLocalHandle <= oldLastHandle; + servicePrivate->endHandle = this->lastLocalHandle; + const bool handleOverflow = this->lastLocalHandle <= oldLastHandle; if (handleOverflow) { qCWarning(QT_BT) << "Not enough attribute handles left to create this service"; - d_ptr->lastLocalHandle = oldLastHandle; + this->lastLocalHandle = oldLastHandle; return nullptr; } - d_ptr->localServices.insert(servicePrivate->uuid, servicePrivate); - d_ptr->addToGenericAttributeList(service, servicePrivate->startHandle); - return new QLowEnergyService(servicePrivate, parent); + this->localServices.insert(servicePrivate->uuid, servicePrivate); + this->addToGenericAttributeList(service, servicePrivate->startHandle); + return new QLowEnergyService(servicePrivate); } /*! diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp index 583c0e27..0744bcc4 100644 --- a/src/bluetooth/qlowenergycontroller_bluez.cpp +++ b/src/bluetooth/qlowenergycontroller_bluez.cpp @@ -530,6 +530,8 @@ void QLowEnergyControllerPrivate::connectToDevice() if (l2cpSocket) delete l2cpSocket; + createServicesForCentralIfRequired(); + // check for active running connections // BlueZ 5.37+ (maybe even earlier versions) can have pending BTLE connections // Only one active L2CP socket to CID 0x4 possible at a time @@ -644,6 +646,72 @@ void QLowEnergyControllerPrivate::establishL2cpClientSocket() loadSigningDataIfNecessary(LocalSigningKey); } +void QLowEnergyControllerPrivate::createServicesForCentralIfRequired() +{ + //only enable when requested + //for now we use env variable to activate the feature + if (Q_LIKELY(!qEnvironmentVariableIsSet("QT_DEFAULT_CENTRAL_SERVICES"))) + return; //nothing to do + + //do not add the services each time we start a connection + if (localServices.contains(QBluetoothUuid(QBluetoothUuid::GenericAccess))) + return; + + qCDebug(QT_BT_BLUEZ) << "Creating default GAP/GATT services"; + + //populate Generic Access service + //for now the values are static + QLowEnergyServiceData gapServiceData; + gapServiceData.setType(QLowEnergyServiceData::ServiceTypePrimary); + gapServiceData.setUuid(QBluetoothUuid::GenericAccess); + + QLowEnergyCharacteristicData gapDeviceName; + gapDeviceName.setUuid(QBluetoothUuid::DeviceName); + gapDeviceName.setProperties(QLowEnergyCharacteristic::Read); + + QBluetoothLocalDevice mainAdapter; + gapDeviceName.setValue(mainAdapter.name().toLatin1()); //static name + + QLowEnergyCharacteristicData gapAppearance; + gapAppearance.setUuid(QBluetoothUuid::Appearance); + gapAppearance.setProperties(QLowEnergyCharacteristic::Read); + gapAppearance.setValue(QByteArray::fromHex("80")); // Generic Computer (0x80) + + QLowEnergyCharacteristicData gapPrivacyFlag; + gapPrivacyFlag.setUuid(QBluetoothUuid::PeripheralPrivacyFlag); + gapPrivacyFlag.setProperties(QLowEnergyCharacteristic::Read); + gapPrivacyFlag.setValue(QByteArray::fromHex("00")); // disable privacy + + gapServiceData.addCharacteristic(gapDeviceName); + gapServiceData.addCharacteristic(gapAppearance); + gapServiceData.addCharacteristic(gapPrivacyFlag); + + Q_Q(QLowEnergyController); + QLowEnergyService *service = addServiceHelper(gapServiceData); + if (service) + service->setParent(q); + + QLowEnergyServiceData gattServiceData; + gattServiceData.setType(QLowEnergyServiceData::ServiceTypePrimary); + gattServiceData.setUuid(QBluetoothUuid::GenericAttribute); + + QLowEnergyCharacteristicData serviceChangedChar; + serviceChangedChar.setUuid(QBluetoothUuid::ServiceChanged); + serviceChangedChar.setProperties(QLowEnergyCharacteristic::Indicate); + //arbitrary range of 2 bit handle range (1-4 + serviceChangedChar.setValue(QByteArray::fromHex("0104")); + + const QLowEnergyDescriptorData clientConfig( + QBluetoothUuid::ClientCharacteristicConfiguration, + QByteArray(2, 0)); + serviceChangedChar.addDescriptor(clientConfig); + gattServiceData.addCharacteristic(serviceChangedChar); + + service = addServiceHelper(gattServiceData); + if (service) + service->setParent(q); +} + void QLowEnergyControllerPrivate::l2cpConnected() { Q_Q(QLowEnergyController); diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index 5811376b..5c680c3b 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -147,6 +147,8 @@ public: QLowEnergyHandle handle); QLowEnergyDescriptor descriptorForHandle( QLowEnergyHandle handle); + QLowEnergyService *addServiceHelper(const QLowEnergyServiceData &service); + quint16 updateValueOfCharacteristic(QLowEnergyHandle charHandle, const QByteArray &value, @@ -416,6 +418,7 @@ private: void restartRequestTimer(); void establishL2cpClientSocket(); + void createServicesForCentralIfRequired(); private slots: void l2cpConnected(); -- cgit v1.2.3