diff options
Diffstat (limited to 'src/bluetooth/qlowenergycontroller_bluezdbus.cpp')
-rw-r--r-- | src/bluetooth/qlowenergycontroller_bluezdbus.cpp | 491 |
1 files changed, 366 insertions, 125 deletions
diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp index 2a0fafdf..a00e4383 100644 --- a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp +++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtBluetooth module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qlowenergycontroller_bluezdbus_p.h" #include "bluez/adapter1_bluez5_p.h" @@ -47,23 +11,66 @@ #include "bluez/battery1_p.h" #include "bluez/objectmanager_p.h" #include "bluez/properties_p.h" - +#include "bluez/bluezperipheralapplication_p.h" +#include "bluez/bluezperipheralconnectionmanager_p.h" QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) -QLowEnergyControllerPrivateBluezDBus::QLowEnergyControllerPrivateBluezDBus() - : QLowEnergyControllerPrivate() +QLowEnergyControllerPrivateBluezDBus::QLowEnergyControllerPrivateBluezDBus( + const QString &adapterPathWithPeripheralSupport) + : QLowEnergyControllerPrivate(), + adapterPathWithPeripheralSupport(adapterPathWithPeripheralSupport) { } QLowEnergyControllerPrivateBluezDBus::~QLowEnergyControllerPrivateBluezDBus() { + if (state != QLowEnergyController::UnconnectedState) { + qCWarning(QT_BT_BLUEZ) << "Low Energy Controller is not Unconnected when deleted." + << "Deleted in state:" << state; + } } void QLowEnergyControllerPrivateBluezDBus::init() { + if (role == QLowEnergyController::PeripheralRole) { + Q_ASSERT(!adapterPathWithPeripheralSupport.isEmpty()); + + peripheralApplication = new QtBluezPeripheralApplication(adapterPathWithPeripheralSupport, + this); + + QObject::connect(peripheralApplication, &QtBluezPeripheralApplication::errorOccurred, this, + &QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationError); + + QObject::connect(peripheralApplication, &QtBluezPeripheralApplication::registered, this, + &QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationRegistered); + + QObject::connect(peripheralApplication, + &QtBluezPeripheralApplication::characteristicValueUpdatedByRemote, this, + &QLowEnergyControllerPrivateBluezDBus::handlePeripheralCharacteristicValueUpdate); + + QObject::connect(peripheralApplication, + &QtBluezPeripheralApplication::descriptorValueUpdatedByRemote, this, + &QLowEnergyControllerPrivateBluezDBus::handlePeripheralDescriptorValueUpdate); + + peripheralConnectionManager = + new QtBluezPeripheralConnectionManager(localAdapter, this); + + QObject::connect(peripheralApplication, + &QtBluezPeripheralApplication::remoteDeviceAccessEvent, + peripheralConnectionManager, + &QtBluezPeripheralConnectionManager::remoteDeviceAccessEvent); + + QObject::connect(peripheralConnectionManager, + &QtBluezPeripheralConnectionManager::connectivityStateChanged, this, + &QLowEnergyControllerPrivateBluezDBus::handlePeripheralConnectivityChanged); + + QObject::connect(peripheralConnectionManager, + &QtBluezPeripheralConnectionManager::remoteDeviceChanged, this, + &QLowEnergyControllerPrivateBluezDBus::handlePeripheralRemoteDeviceChanged); + } } void QLowEnergyControllerPrivateBluezDBus::devicePropertiesChanged( @@ -112,32 +119,49 @@ void QLowEnergyControllerPrivateBluezDBus::devicePropertiesChanged( } } } + + if (changedProperties.contains(QStringLiteral("UUIDs"))) { + const QStringList newUuidStringList = changedProperties.value(QStringLiteral("UUIDs")).toStringList(); + QList<QBluetoothUuid> newUuidList; + for (const QString &uuidString : newUuidStringList) + newUuidList.append(QBluetoothUuid(uuidString)); + + for (const QBluetoothUuid &uuid : serviceList.keys()) { + if (!newUuidList.contains(uuid)) { + qCDebug(QT_BT_BLUEZ) << __func__ << "Service" << uuid << "has been removed"; + QSharedPointer<QLowEnergyServicePrivate> service = serviceList.take(uuid); + service->setController(nullptr); + dbusServices.remove(uuid); + } + } + } + } else if (interface == QStringLiteral("org.bluez.Battery1")) { qCDebug(QT_BT_BLUEZ) << "######" << interface << changedProperties; if (changedProperties.contains(QStringLiteral("Percentage"))) { // if battery service is discovered and ClientCharConfig is enabled // emit characteristicChanged() signal - const QBluetoothUuid uuid(QBluetoothUuid::BatteryService); + const QBluetoothUuid uuid(QBluetoothUuid::ServiceClassUuid::BatteryService); if (!serviceList.contains(uuid) || !dbusServices.contains(uuid) || !dbusServices[uuid].hasBatteryService || dbusServices[uuid].batteryInterface.isNull()) return; QSharedPointer<QLowEnergyServicePrivate> serviceData = serviceList.value(uuid); - if (serviceData->state != QLowEnergyService::ServiceDiscovered) + if (serviceData->state != QLowEnergyService::RemoteServiceDiscovered) return; QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData>::iterator iter; iter = serviceData->characteristicList.begin(); while (iter != serviceData->characteristicList.end()) { auto &charData = iter.value(); - if (charData.uuid != QBluetoothUuid::BatteryLevel) + if (charData.uuid != QBluetoothUuid::CharacteristicType::BatteryLevel) continue; // Client Characteristic Notification enabled? bool cccActive = false; - for (const QLowEnergyServicePrivate::DescData &descData : qAsConst(charData.descriptorList)) { - if (descData.uuid != QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) + for (const QLowEnergyServicePrivate::DescData &descData : std::as_const(charData.descriptorList)) { + if (descData.uuid != QBluetoothUuid(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration)) continue; if (descData.value == QByteArray::fromHex("0100") || descData.value == QByteArray::fromHex("0200")) { @@ -177,7 +201,7 @@ void QLowEnergyControllerPrivateBluezDBus::characteristicPropertiesChanged( const QLowEnergyCharacteristic changedChar = characteristicForHandle(charHandle); const QLowEnergyDescriptor ccnDescriptor = changedChar.descriptor( - QBluetoothUuid::ClientCharacteristicConfiguration); + QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration); if (!ccnDescriptor.isValid()) return; @@ -191,12 +215,17 @@ void QLowEnergyControllerPrivateBluezDBus::characteristicPropertiesChanged( emit service->characteristicChanged(changedChar, newValue); } -void QLowEnergyControllerPrivateBluezDBus::interfacesRemoved( - const QDBusObjectPath &objectPath, const QStringList &/*interfaces*/) +void QLowEnergyControllerPrivateBluezDBus::interfacesRemoved(const QDBusObjectPath &objectPath, + const QStringList &interfaces) { if (objectPath.path() == device->path()) { - qCWarning(QT_BT_BLUEZ) << "DBus Device1 was removed"; - executeClose(QLowEnergyController::UnknownRemoteDeviceError); + if (interfaces.contains(QStringLiteral("org.bluez.Device1"))) { + qCWarning(QT_BT_BLUEZ) << "DBus Device1 was removed"; + executeClose(QLowEnergyController::UnknownRemoteDeviceError); + } else { + qCDebug(QT_BT_BLUEZ) << "DBus interfaces" << interfaces << "were removed from" + << objectPath.path(); + } } else if (objectPath.path() == adapter->path()) { qCWarning(QT_BT_BLUEZ) << "DBus Adapter was removed"; executeClose(QLowEnergyController::InvalidBluetoothAdapterError); @@ -225,6 +254,20 @@ void QLowEnergyControllerPrivateBluezDBus::resetController() deviceMonitor = nullptr; } + if (advertiser) { + delete advertiser; + advertiser = nullptr; + } + + if (peripheralApplication) + peripheralApplication->reset(); + + if (peripheralConnectionManager) + peripheralConnectionManager->reset(); + + remoteName.clear(); + remoteMtu = -1; + dbusServices.clear(); jobs.clear(); invalidateServices(); @@ -245,10 +288,8 @@ void QLowEnergyControllerPrivateBluezDBus::connectToDeviceHelper() return; } - QScopedPointer<OrgFreedesktopDBusObjectManagerInterface> manager( - new OrgFreedesktopDBusObjectManagerInterface( - QStringLiteral("org.bluez"), QStringLiteral("/"), - QDBusConnection::systemBus())); + auto manager = std::make_unique<OrgFreedesktopDBusObjectManagerInterface>( + QStringLiteral("org.bluez"), QStringLiteral("/"), QDBusConnection::systemBus()); QDBusPendingReply<ManagedObjectList> reply = manager->GetManagedObjects(); reply.waitForFinished(); @@ -290,7 +331,7 @@ void QLowEnergyControllerPrivateBluezDBus::connectToDeviceHelper() return; } - managerBluez = manager.take(); + managerBluez = manager.release(); connect(managerBluez, &OrgFreedesktopDBusObjectManagerInterface::InterfacesRemoved, this, &QLowEnergyControllerPrivateBluezDBus::interfacesRemoved); adapter = new OrgBluezAdapter1Interface( @@ -354,24 +395,37 @@ void QLowEnergyControllerPrivateBluezDBus::connectToDevice() void QLowEnergyControllerPrivateBluezDBus::disconnectFromDevice() { - if (!device) - return; - - setState(QLowEnergyController::ClosingState); + if (role == QLowEnergyController::CentralRole) { + if (!device) + return; - QDBusPendingReply<> reply = device->Disconnect(); - QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); - connect(watcher, &QDBusPendingCallWatcher::finished, this, - [this](QDBusPendingCallWatcher* call) { - QDBusPendingReply<> reply = *call; - if (reply.isError()) { - qCDebug(QT_BT_BLUEZ) << "BTLE_DBUS::disconnect() failed" - << reply.reply().errorName() - << reply.reply().errorMessage(); - executeClose(QLowEnergyController::NoError); - } - call->deleteLater(); - }); + setState(QLowEnergyController::ClosingState); + + QDBusPendingReply<> reply = device->Disconnect(); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, + [this](QDBusPendingCallWatcher* call) { + QDBusPendingReply<> reply = *call; + if (reply.isError()) { + qCDebug(QT_BT_BLUEZ) << "BTLE_DBUS::disconnect() failed" + << reply.reply().errorName() + << reply.reply().errorMessage(); + executeClose(QLowEnergyController::UnknownError); + } else { + executeClose(QLowEnergyController::NoError); + } + call->deleteLater(); + }); + } else { + Q_Q(QLowEnergyController); + peripheralConnectionManager->disconnectDevices(); + resetController(); + remoteDevice.clear(); + const auto emitDisconnected = (state == QLowEnergyController::ConnectedState); + setState(QLowEnergyController::UnconnectedState); + if (emitDisconnected) + emit q->disconnected(); + } } void QLowEnergyControllerPrivateBluezDBus::discoverServices() @@ -388,7 +442,8 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServices() Q_Q(QLowEnergyController); auto setupServicePrivate = [&, q]( - QLowEnergyService::ServiceType type, const QBluetoothUuid &uuid, const QString &path){ + QLowEnergyService::ServiceType type, const QBluetoothUuid &uuid, + const QString &path, const bool battery1Interface = false){ QSharedPointer<QLowEnergyServicePrivate> priv = QSharedPointer<QLowEnergyServicePrivate>::create(); priv->uuid = uuid; priv->type = type; // we make a guess we cannot validate @@ -396,8 +451,11 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServices() GattService serviceContainer; serviceContainer.servicePath = path; - if (uuid == QBluetoothUuid::BatteryService) + + if (battery1Interface) { + qCDebug(QT_BT_BLUEZ) << "Using Battery1 interface to emulate generic interface"; serviceContainer.hasBatteryService = true; + } serviceList.insert(priv->uuid, priv); dbusServices.insert(priv->uuid, serviceContainer); @@ -407,25 +465,50 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServices() const ManagedObjectList managedObjectList = reply.value(); const QString servicePathPrefix = device->path().append(QStringLiteral("/service")); + + // The Bluez battery service (0x180f) support has evolved over time and needs additional logic: + // + // * Until 5.47 Bluez exposes battery services via generic interface (GattService1) only + // * Between 5.48..5.54 Bluez exposes battery service as a dedicated 'Battery1' interface only + // * From 5.55 Bluez exposes both the generic service as well as the dedicated 'Battery1' + // + // To hide the difference from users the 'Battery1' interface will be used to emulate the + // generic interface. Importantly also the GattService1 interface, if available, is available + // early whereas the Battery1 interface may be available only later (generated too late for + // this service discovery's purposes) + // + // The precedence for battery service here is as follows: + // * If available via GattService1, use that and ignore possible Battery1 interface + // * If Battery1 interface is available or the 'org.bluez.Device1' lists battery service + // amongst list of available services, mark the service such that the code will later + // look up the Battery1 service details + bool gattBatteryService{false}; + QString batteryServicePath; + for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) { const InterfaceList &ifaceList = it.value(); if (!it.key().path().startsWith(device->path())) continue; - // Since Bluez 5.48 battery services (0x180f) are no longer exposed - // as generic services under servicePathPrefix. - // A dedicated org.bluez.Battery1 interface is exposed. Here we are going to revert - // Bettery1 to the generic pattern. if (it.key().path() == device->path()) { - // find Battery1 service + // See if the battery service is available or is assumed to be available later for (InterfaceList::const_iterator battIter = ifaceList.constBegin(); battIter != ifaceList.constEnd(); ++battIter) { const QString &iface = battIter.key(); if (iface == QStringLiteral("org.bluez.Battery1")) { - qCDebug(QT_BT_BLUEZ) << "Found dedicated Battery service -> emulating generic btle access"; - setupServicePrivate(QLowEnergyService::PrimaryService, - QBluetoothUuid::BatteryService, - it.key().path()); + qCDebug(QT_BT_BLUEZ) << "Dedicated Battery1 service available"; + batteryServicePath = it.key().path(); + break; + } else if (iface == QStringLiteral("org.bluez.Device1")) { + for (auto const& uuid : + battIter.value()[QStringLiteral("UUIDs")].toStringList()) { + if (QBluetoothUuid(uuid) == + QBluetoothUuid::ServiceClassUuid::BatteryService) { + qCDebug(QT_BT_BLUEZ) << "Battery service listed as available service"; + batteryServicePath = it.key().path(); + break; + } + } } } continue; @@ -441,6 +524,11 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServices() QScopedPointer<OrgBluezGattService1Interface> service(new OrgBluezGattService1Interface( QStringLiteral("org.bluez"),it.key().path(), QDBusConnection::systemBus(), this)); + if (QBluetoothUuid(service->uUID()) == + QBluetoothUuid::ServiceClassUuid::BatteryService) { + qCDebug(QT_BT_BLUEZ) << "Using battery service via GattService1 interface"; + gattBatteryService = true; + } setupServicePrivate(service->primary() ? QLowEnergyService::PrimaryService : QLowEnergyService::IncludedService, @@ -449,6 +537,12 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServices() } } + if (!gattBatteryService && !batteryServicePath.isEmpty()) { + setupServicePrivate(QLowEnergyService::PrimaryService, + QBluetoothUuid::ServiceClassUuid::BatteryService, + batteryServicePath, true); + } + setState(QLowEnergyController::DiscoveredState); emit q->discoveryFinished(); } @@ -474,19 +568,19 @@ void QLowEnergyControllerPrivateBluezDBus::discoverBatteryServiceDetails( charData.valueHandle = runningHandle++; charData.properties.setFlag(QLowEnergyCharacteristic::Read); charData.properties.setFlag(QLowEnergyCharacteristic::Notify); - charData.uuid = QBluetoothUuid::BatteryLevel; + charData.uuid = QBluetoothUuid::CharacteristicType::BatteryLevel; charData.value = QByteArray(1, char(batteryService->percentage())); // Create the descriptors for the BatteryLevel // They are hardcoded although CCC may change QLowEnergyServicePrivate::DescData descData; QLowEnergyHandle descriptorHandle = runningHandle++; - descData.uuid = QBluetoothUuid::ClientCharacteristicConfiguration; + descData.uuid = QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration; descData.value = QByteArray::fromHex("0000"); // all configs off charData.descriptorList.insert(descriptorHandle, descData); descriptorHandle = runningHandle++; - descData.uuid = QBluetoothUuid::CharacteristicPresentationFormat; + descData.uuid = QBluetoothUuid::DescriptorType::CharacteristicPresentationFormat; //for details see Characteristic Presentation Format Vol3, Part G 3.3.3.5 // unsigend 8 bit, exp=1, org.bluetooth.unit.percentage, namespace & description // bit order: little endian @@ -494,14 +588,14 @@ void QLowEnergyControllerPrivateBluezDBus::discoverBatteryServiceDetails( charData.descriptorList.insert(descriptorHandle, descData); descriptorHandle = runningHandle++; - descData.uuid = QBluetoothUuid::ReportReference; + descData.uuid = QBluetoothUuid::DescriptorType::ReportReference; descData.value = QByteArray::fromHex("0401"); charData.descriptorList.insert(descriptorHandle, descData); serviceData->characteristicList[indexHandle] = charData; serviceData->endHandle = runningHandle++; - serviceData->setState(QLowEnergyService::ServiceDiscovered); + serviceData->setState(QLowEnergyService::RemoteServiceDiscovered); } void QLowEnergyControllerPrivateBluezDBus::executeClose(QLowEnergyController::Error newError) @@ -519,7 +613,8 @@ void QLowEnergyControllerPrivateBluezDBus::executeClose(QLowEnergyController::Er } } -void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoothUuid &service) +void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails( + const QBluetoothUuid &service, QLowEnergyService::DiscoveryMode mode) { if (!serviceList.contains(service) || !dbusServices.contains(service)) { qCWarning(QT_BT_BLUEZ) << "Discovery of unknown service" << service.toString() @@ -622,7 +717,8 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoo charData.uuid = QBluetoothUuid(dbusChar.characteristic->uUID()); // schedule read for initial char value - if (charData.properties.testFlag(QLowEnergyCharacteristic::Read)) { + if (mode == QLowEnergyService::FullDiscovery + && charData.properties.testFlag(QLowEnergyCharacteristic::Read)) { GattJob job; job.flags = GattJob::JobFlags({GattJob::CharRead, GattJob::ServiceDiscovery}); job.service = serviceData; @@ -631,7 +727,7 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoo } // descriptor data - for (const auto &descEntry : qAsConst(dbusChar.descriptors)) { + for (const auto &descEntry : std::as_const(dbusChar.descriptors)) { const QLowEnergyHandle descriptorHandle = runningHandle++; QLowEnergyServicePrivate::DescData descData; descData.uuid = QBluetoothUuid(descEntry->uUID()); @@ -640,7 +736,7 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoo // every ClientCharacteristicConfiguration needs to track property changes if (descData.uuid - == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { + == QBluetoothUuid(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration)) { dbusChar.charMonitor = QSharedPointer<OrgFreedesktopDBusPropertiesInterface>::create( QStringLiteral("org.bluez"), dbusChar.characteristic->path(), @@ -654,12 +750,14 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoo }); } - // schedule read for initial descriptor value - GattJob job; - job.flags = GattJob::JobFlags({GattJob::DescRead, GattJob::ServiceDiscovery}); - job.service = serviceData; - job.handle = descriptorHandle; - jobs.append(job); + if (mode == QLowEnergyService::FullDiscovery) { + // schedule read for initial descriptor value + GattJob job; + job.flags = GattJob::JobFlags({ GattJob::DescRead, GattJob::ServiceDiscovery }); + job.service = serviceData; + job.handle = descriptorHandle; + jobs.append(job); + } } serviceData->characteristicList[indexHandle] = charData; @@ -672,7 +770,7 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoo GattJob &lastJob = jobs.last(); lastJob.flags.setFlag(GattJob::LastServiceDiscovery, true); } else { - serviceData->setState(QLowEnergyService::ServiceDiscovered); + serviceData->setState(QLowEnergyService::RemoteServiceDiscovered); } scheduleNextJob(); @@ -723,7 +821,7 @@ void QLowEnergyControllerPrivateBluezDBus::onCharReadFinished(QDBusPendingCallWa if (isServiceDiscovery) { if (nextJob.flags.testFlag(GattJob::LastServiceDiscovery)) - service->setState(QLowEnergyService::ServiceDiscovered); + service->setState(QLowEnergyService::RemoteServiceDiscovered); } else { QLowEnergyCharacteristic ch(service, nextJob.handle); emit service->characteristicRead(ch, reply.value()); @@ -773,7 +871,6 @@ void QLowEnergyControllerPrivateBluezDBus::onDescReadFinished(QDBusPendingCallWa } bool isServiceDiscovery = nextJob.flags.testFlag(GattJob::ServiceDiscovery); - const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid; QDBusPendingReply<QByteArray> reply = *call; if (reply.isError()) { @@ -789,7 +886,7 @@ void QLowEnergyControllerPrivateBluezDBus::onDescReadFinished(QDBusPendingCallWa if (isServiceDiscovery) { if (nextJob.flags.testFlag(GattJob::LastServiceDiscovery)) - service->setState(QLowEnergyService::ServiceDiscovered); + service->setState(QLowEnergyService::RemoteServiceDiscovered); } else { QLowEnergyDescriptor desc(service, ch.attributeHandle(), nextJob.handle); emit service->descriptorRead(desc, reply.value()); @@ -834,7 +931,7 @@ void QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished(QDBusPendingCallW updateValueOfCharacteristic(nextJob.handle, nextJob.value, false); QLowEnergyCharacteristic ch(service, nextJob.handle); - // write without respone implies zero feedback + // write without response implies zero feedback if (nextJob.writeMode == QLowEnergyService::WriteWithResponse) { qCDebug(QT_BT_BLUEZ) << "Written Char:" << charData.uuid << nextJob.value.toHex(); emit service->characteristicWritten(ch, nextJob.value); @@ -921,7 +1018,7 @@ void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob() const QLowEnergyServicePrivate::CharData &charData = service->characteristicList.value(nextJob.handle); bool foundChar = false; - for (const auto &gattChar : qAsConst(dbusServiceData.characteristics)) { + for (const auto &gattChar : std::as_const(dbusServiceData.characteristics)) { if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID())) continue; @@ -950,11 +1047,16 @@ void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob() const QLowEnergyServicePrivate::CharData &charData = service->characteristicList.value(nextJob.handle); bool foundChar = false; - for (const auto &gattChar : qAsConst(dbusServiceData.characteristics)) { + for (const auto &gattChar : std::as_const(dbusServiceData.characteristics)) { if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID())) continue; - QDBusPendingReply<> reply = gattChar.characteristic->WriteValue(nextJob.value, QVariantMap()); + QVariantMap options; + // The "type" option only works with BlueZ >= 5.50, older versions always write with response + options[QStringLiteral("type")] = nextJob.writeMode == QLowEnergyService::WriteWithoutResponse ? + QStringLiteral("command") : QStringLiteral("request"); + QDBusPendingReply<> reply = gattChar.characteristic->WriteValue(nextJob.value, options); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished); @@ -987,11 +1089,11 @@ void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob() const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid; bool foundDesc = false; - for (const auto &gattChar : qAsConst(dbusServiceData.characteristics)) { + for (const auto &gattChar : std::as_const(dbusServiceData.characteristics)) { if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID())) continue; - for (const auto &gattDesc : qAsConst(gattChar.descriptors)) { + for (const auto &gattDesc : std::as_const(gattChar.descriptors)) { if (descUuid != QBluetoothUuid(gattDesc->uUID())) continue; @@ -1031,17 +1133,17 @@ void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob() const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid; bool foundDesc = false; - for (const auto &gattChar : qAsConst(dbusServiceData.characteristics)) { + for (const auto &gattChar : std::as_const(dbusServiceData.characteristics)) { if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID())) continue; - for (const auto &gattDesc : qAsConst(gattChar.descriptors)) { + for (const auto &gattDesc : std::as_const(gattChar.descriptors)) { if (descUuid != QBluetoothUuid(gattDesc->uUID())) continue; //notifications enabled via characteristics Start/StopNotify() functions //otherwise regular WriteValue() calls on descriptor interface - if (descUuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { + if (descUuid == QBluetoothUuid(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration)) { const QByteArray value = nextJob.value; QDBusPendingReply<> reply; @@ -1190,8 +1292,16 @@ void QLowEnergyControllerPrivateBluezDBus::writeCharacteristic( scheduleNextJob(); } else { - qWarning(QT_BT_BLUEZ) << "writeCharacteristic() not implemented for DBus Bluez GATT"; - service->setError(QLowEnergyService::CharacteristicWriteError); + // Peripheral role + Q_ASSERT(peripheralApplication); + if (!peripheralApplication->localCharacteristicWrite(charHandle, newValue)) { + qCWarning(QT_BT_BLUEZ) << "Characteristic write failed" + << characteristicForHandle(charHandle).uuid(); + service->setError(QLowEnergyService::CharacteristicWriteError); + return; + } + QLowEnergyServicePrivate::CharData &charData = service->characteristicList[charHandle]; + charData.value = newValue; } } @@ -1212,7 +1322,7 @@ void QLowEnergyControllerPrivateBluezDBus::writeDescriptor( if (!descriptor.isValid()) return; - if (descriptor.uuid() == QBluetoothUuid::ClientCharacteristicConfiguration) { + if (descriptor.uuid() == QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration) { if (newValue == QByteArray::fromHex("0000") || newValue == QByteArray::fromHex("0100") || newValue == QByteArray::fromHex("0200")) { @@ -1239,37 +1349,168 @@ void QLowEnergyControllerPrivateBluezDBus::writeDescriptor( scheduleNextJob(); } else { - qWarning(QT_BT_BLUEZ) << "writeDescriptor() peripheral not implemented for DBus Bluez GATT"; - service->setError(QLowEnergyService::CharacteristicWriteError); + // Peripheral role + Q_ASSERT(peripheralApplication); + + auto desc = descriptorForHandle(descriptorHandle); + if (desc.uuid() == QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration) { + qCWarning(QT_BT_BLUEZ) << "CCCD write not supported in peripheral role"; + service->setError(QLowEnergyService::DescriptorWriteError); + return; + } else if (!peripheralApplication->localDescriptorWrite(descriptorHandle, newValue)) { + qCWarning(QT_BT_BLUEZ) << "Descriptor write failed" << desc.uuid(); + service->setError(QLowEnergyService::DescriptorWriteError); + return; + } + service->characteristicList[charHandle].descriptorList[descriptorHandle].value = newValue; } } void QLowEnergyControllerPrivateBluezDBus::startAdvertising( - const QLowEnergyAdvertisingParameters &/* params */, - const QLowEnergyAdvertisingData &/* advertisingData */, - const QLowEnergyAdvertisingData &/* scanResponseData */) + const QLowEnergyAdvertisingParameters ¶ms, + const QLowEnergyAdvertisingData &advertisingData, + const QLowEnergyAdvertisingData &scanResponseData) { + error = QLowEnergyController::NoError; + errorString.clear(); + + Q_ASSERT(peripheralApplication); + Q_ASSERT(!adapterPathWithPeripheralSupport.isEmpty()); + + if (advertiser) { + // Clear any previous advertiser in case advertising data has changed. + // For clarity: this function is called only in 'Unconnected' state + delete advertiser; + advertiser = nullptr; + } + advertiser = new QLeDBusAdvertiser(params, advertisingData, scanResponseData, + adapterPathWithPeripheralSupport, this); + connect(advertiser, &QLeDBusAdvertiser::errorOccurred, + this, &QLowEnergyControllerPrivateBluezDBus::handleAdvertisingError); + + setState(QLowEnergyController::AdvertisingState); + + // First register the application to bluez if needed, and then start the advertisement. + // The application registration may fail and is asynchronous => serialize the steps. + // For clarity: advertisements can be used without any services, but registering such + // application to Bluez would fail + if (peripheralApplication->registrationNeeded()) + peripheralApplication->registerApplication(); + else + advertiser->startAdvertising(); } void QLowEnergyControllerPrivateBluezDBus::stopAdvertising() { + // This function is called only in Advertising state + setState(QLowEnergyController::UnconnectedState); + if (advertiser) { + advertiser->stopAdvertising(); + delete advertiser; + advertiser = nullptr; + } +} + +void QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationRegistered() +{ + // Start the actual advertising now that the application is registered. + // Check the state first in case user has called stopAdvertising() during + // application registration + if (advertiser && state == QLowEnergyController::AdvertisingState) + advertiser->startAdvertising(); + else + peripheralApplication->unregisterApplication(); +} + +void QLowEnergyControllerPrivateBluezDBus::handlePeripheralCharacteristicValueUpdate( + QLowEnergyHandle handle, const QByteArray& value) +{ + const auto characteristic = characteristicForHandle(handle); + if (characteristic.d_ptr + && updateValueOfCharacteristic(handle, value, false) == value.size()) { + emit characteristic.d_ptr->characteristicChanged(characteristic, value); + } else { + qCWarning(QT_BT_BLUEZ) << "Remote characteristic write failed"; + } +} + +void QLowEnergyControllerPrivateBluezDBus::handlePeripheralDescriptorValueUpdate( + QLowEnergyHandle characteristicHandle, + QLowEnergyHandle descriptorHandle, + const QByteArray& value) +{ + const auto descriptor = descriptorForHandle(descriptorHandle); + if (descriptor.d_ptr && updateValueOfDescriptor( + characteristicHandle, descriptorHandle, value, false) == value.size()) { + emit descriptor.d_ptr->descriptorWritten(descriptor, value); + } else { + qCWarning(QT_BT_BLUEZ) << "Remote descriptor write failed"; + } +} + +void QLowEnergyControllerPrivateBluezDBus::handlePeripheralRemoteDeviceChanged( + const QBluetoothAddress& address, + const QString& name, + quint16 mtu) +{ + remoteDevice = address; + remoteName = name; + remoteMtu = mtu; +} + +void QLowEnergyControllerPrivateBluezDBus::handleAdvertisingError() +{ + Q_ASSERT(peripheralApplication); + qCWarning(QT_BT_BLUEZ) << "An advertising error occurred"; + setError(QLowEnergyController::AdvertisingError); + setState(QLowEnergyController::UnconnectedState); + peripheralApplication->unregisterApplication(); +} + +void QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationError() +{ + qCWarning(QT_BT_BLUEZ) << "A Bluez peripheral application error occurred"; + setError(QLowEnergyController::UnknownError); + setState(QLowEnergyController::UnconnectedState); +} + +void QLowEnergyControllerPrivateBluezDBus::handlePeripheralConnectivityChanged(bool connected) +{ + Q_Q(QLowEnergyController); + qCDebug(QT_BT_BLUEZ) << "Peripheral application connected change to:" << connected; + if (connected) { + setState(QLowEnergyController::ConnectedState); + } else { + resetController(); + remoteDevice.clear(); + setState(QLowEnergyController::UnconnectedState); + emit q->disconnected(); + } } void QLowEnergyControllerPrivateBluezDBus::requestConnectionUpdate( const QLowEnergyConnectionParameters & /* params */) { + qCWarning(QT_BT_BLUEZ) << "Connection udpate requests not supported on Bluez DBus"; } void QLowEnergyControllerPrivateBluezDBus::addToGenericAttributeList( - const QLowEnergyServiceData &/* service */, - QLowEnergyHandle /* startHandle */) + const QLowEnergyServiceData &serviceData, + QLowEnergyHandle startHandle) { + Q_ASSERT(peripheralApplication); + QSharedPointer<QLowEnergyServicePrivate> servicePrivate = serviceForHandle(startHandle); + if (servicePrivate.isNull()) + return; + peripheralApplication->addService(serviceData, servicePrivate, startHandle); } -QLowEnergyService *QLowEnergyControllerPrivateBluezDBus::addServiceHelper( - const QLowEnergyServiceData &/*service*/) +int QLowEnergyControllerPrivateBluezDBus::mtu() const { - return nullptr; + // currently only supported on peripheral role + return remoteMtu; } QT_END_NAMESPACE + +#include "moc_qlowenergycontroller_bluezdbus_p.cpp" |