From 59ae3cc2ac7baee7e9df1e6ceffcfc882fbe6121 Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Tue, 24 Oct 2017 16:46:03 +0200 Subject: Introduce Base class for QLowEnergyControllerPrivate This permits alternative implementations selectable at runtime. Currently this is only used by Bluez. Change-Id: I3ddeb7f888f3b09bdc62f10d5b9a36320500f329 Reviewed-by: Timur Pocheptsov --- src/bluetooth/qlowenergycontroller_bluezdbus.cpp | 140 +++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 src/bluetooth/qlowenergycontroller_bluezdbus.cpp (limited to 'src/bluetooth/qlowenergycontroller_bluezdbus.cpp') diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp new file mode 100644 index 00000000..dc8fc721 --- /dev/null +++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qlowenergycontroller_bluezdbus_p.h" + + +QT_BEGIN_NAMESPACE + +QLowEnergyControllerPrivateBluezDBus::QLowEnergyControllerPrivateBluezDBus() + : QLowEnergyControllerPrivateBase() +{ +} + +QLowEnergyControllerPrivateBluezDBus::~QLowEnergyControllerPrivateBluezDBus() +{ +} + +void QLowEnergyControllerPrivateBluezDBus::init() +{ +} + +void QLowEnergyControllerPrivateBluezDBus::connectToDevice() +{ + qWarning() << "QLowEnergyControllerPrivateBluezDBus::connectToDevice(): Not implemented"; + //setError(QLowEnergyController::UnknownError); +} + +void QLowEnergyControllerPrivateBluezDBus::disconnectFromDevice() +{ + +} + +void QLowEnergyControllerPrivateBluezDBus::discoverServices() +{ + +} + +void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoothUuid &/*service*/) +{ + +} + +void QLowEnergyControllerPrivateBluezDBus::readCharacteristic( + const QSharedPointer /*service*/, + const QLowEnergyHandle /*charHandle*/) +{ + +} + +void QLowEnergyControllerPrivateBluezDBus::readDescriptor( + const QSharedPointer /*service*/, + const QLowEnergyHandle /*charHandle*/, + const QLowEnergyHandle /*descriptorHandle*/) +{ + +} + +void QLowEnergyControllerPrivateBluezDBus::writeCharacteristic( + const QSharedPointer /*service*/, + const QLowEnergyHandle /*charHandle*/, + const QByteArray &/*newValue*/, + QLowEnergyService::WriteMode /*writeMode*/) + { + +} + +void QLowEnergyControllerPrivateBluezDBus::writeDescriptor( + const QSharedPointer /*service*/, + const QLowEnergyHandle /*charHandle*/, + const QLowEnergyHandle /*descriptorHandle*/, + const QByteArray &/*newValue*/) +{ + +} + +void QLowEnergyControllerPrivateBluezDBus::startAdvertising( + const QLowEnergyAdvertisingParameters &/* params */, + const QLowEnergyAdvertisingData &/* advertisingData */, + const QLowEnergyAdvertisingData &/* scanResponseData */) +{ +} + +void QLowEnergyControllerPrivateBluezDBus::stopAdvertising() +{ +} + +void QLowEnergyControllerPrivateBluezDBus::requestConnectionUpdate( + const QLowEnergyConnectionParameters & /* params */) +{ +} + +void QLowEnergyControllerPrivateBluezDBus::addToGenericAttributeList( + const QLowEnergyServiceData &/* service */, + QLowEnergyHandle /* startHandle */) +{ +} + +QLowEnergyService *QLowEnergyControllerPrivateBluezDBus::addServiceHelper( + const QLowEnergyServiceData &/*service*/) +{ + return nullptr; +} + +QT_END_NAMESPACE -- cgit v1.2.3 From fbf36811672de79f2a648af97ca73d6c0e3b1608 Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Wed, 25 Oct 2017 14:49:47 +0200 Subject: Rename various QLEControllerPrivate classes The base class is renamed to QLEControllerPrivate and the existing QLEControllerPrivate becomes QLEControllerPrivateCommon. This is necessary to re-enable Q_DECLARE_PRIVATE. The macro uses by convention the "Private" class prefix which is currently broken because not every implementation uses QLEControllerPrivate as d-pointer type. This also avoids a SC/BC break in qlowenergycontroller.h as the d-pointer remains the same and the functions declared via Q_DECLARE_PRIVATE still return the same type. Change-Id: I84890b06280b2c473a4d370606d3bbc58a258eea Reviewed-by: Oliver Wolff --- src/bluetooth/qlowenergycontroller_bluezdbus.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/bluetooth/qlowenergycontroller_bluezdbus.cpp') diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp index dc8fc721..95721e0c 100644 --- a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp +++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp @@ -43,7 +43,7 @@ QT_BEGIN_NAMESPACE QLowEnergyControllerPrivateBluezDBus::QLowEnergyControllerPrivateBluezDBus() - : QLowEnergyControllerPrivateBase() + : QLowEnergyControllerPrivate() { } -- cgit v1.2.3 From da7a013dd2aa7bed9f4a6b5122b8f9dadcb252ab Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Wed, 29 Nov 2017 15:15:51 +0100 Subject: BTLE DBus: Add ability to connect/disconnect to remote device The patch uses the Device1 DBus API to connect and disconnect to a remote BTLE device. In addition, status and error condition tracking is added to adapt to various error cases. Task-number: QTBUG-46819 Change-Id: I0671df5596882c89aeead89c05ddcc855f8ba375 Reviewed-by: Oliver Wolff --- src/bluetooth/qlowenergycontroller_bluezdbus.cpp | 244 ++++++++++++++++++++++- 1 file changed, 242 insertions(+), 2 deletions(-) (limited to 'src/bluetooth/qlowenergycontroller_bluezdbus.cpp') diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp index 95721e0c..1829f153 100644 --- a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp +++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp @@ -38,10 +38,17 @@ ****************************************************************************/ #include "qlowenergycontroller_bluezdbus_p.h" +#include "bluez/adapter1_bluez5_p.h" +#include "bluez/bluez5_helper_p.h" +#include "bluez/device1_bluez5_p.h" +#include "bluez/objectmanager_p.h" +#include "bluez/properties_p.h" QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) + QLowEnergyControllerPrivateBluezDBus::QLowEnergyControllerPrivateBluezDBus() : QLowEnergyControllerPrivate() { @@ -55,15 +62,248 @@ void QLowEnergyControllerPrivateBluezDBus::init() { } +void QLowEnergyControllerPrivateBluezDBus::devicePropertiesChanged( + const QString &interface, const QVariantMap &changedProperties, + const QStringList &/*removedProperties*/) +{ + if (interface == QStringLiteral("org.bluez.Device1")) { + qCDebug(QT_BT_BLUEZ) << "######" << interface << changedProperties; + if (changedProperties.contains(QStringLiteral("ServicesResolved"))) { + //we could check for Connected property as well, but we choose to wait + //for ServicesResolved being true + + if (pendingConnect) { + bool isResolved = changedProperties.value(QStringLiteral("ServicesResolved")).toBool(); + if (isResolved) { + setState(QLowEnergyController::ConnectedState); + pendingConnect = false; + disconnectSignalRequired = true; + Q_Q(QLowEnergyController); + emit q->connected(); + } + } + } + + if (changedProperties.contains(QStringLiteral("Connected"))) { + bool isConnected = changedProperties.value(QStringLiteral("Connected")).toBool(); + if (!isConnected) { + switch (state) { + case QLowEnergyController::ConnectingState: + case QLowEnergyController::ConnectedState: + case QLowEnergyController::DiscoveringState: + case QLowEnergyController::DiscoveredState: + case QLowEnergyController::ClosingState: + { + bool emitDisconnect = disconnectSignalRequired; + bool emitError = pendingConnect; + + resetController(); + + if (emitError) + setError(QLowEnergyController::ConnectionError); + setState(QLowEnergyController::UnconnectedState); + + if (emitDisconnect) { + Q_Q(QLowEnergyController); + emit q->disconnected(); + } + } + break; + case QLowEnergyController::AdvertisingState: + case QLowEnergyController::UnconnectedState: + //ignore + break; + } + } + } + } +} + +void QLowEnergyControllerPrivateBluezDBus::interfacesRemoved( + const QDBusObjectPath &objectPath, const QStringList &/*interfaces*/) +{ + if (objectPath.path() == device->path()) { + resetController(); + setError(QLowEnergyController::UnknownRemoteDeviceError); + qCWarning(QT_BT_BLUEZ) << "DBus Device1 was removed"; + setState(QLowEnergyController::UnconnectedState); + } else if (objectPath.path() == adapter->path()) { + resetController(); + setError(QLowEnergyController::InvalidBluetoothAdapterError); + qCWarning(QT_BT_BLUEZ) << "DBus Adapter was removed"; + setState(QLowEnergyController::UnconnectedState); + } +} + +void QLowEnergyControllerPrivateBluezDBus::resetController() +{ + if (managerBluez) { + delete managerBluez; + managerBluez = nullptr; + } + + if (adapter) { + delete adapter; + adapter = nullptr; + } + + if (device) { + delete device; + device = nullptr; + } + + if (deviceMonitor) { + delete deviceMonitor; + deviceMonitor = nullptr; + } + + pendingConnect = pendingDisconnect = disconnectSignalRequired = false; +} + +void QLowEnergyControllerPrivateBluezDBus::connectToDeviceHelper() +{ + resetController(); + + bool ok = false; + const QString hostAdapterPath = findAdapterForAddress(localAdapter, &ok); + if (!ok || hostAdapterPath.isEmpty()) { + qCWarning(QT_BT_BLUEZ) << "Cannot find suitable bluetooth adapter"; + setError(QLowEnergyController::InvalidBluetoothAdapterError); + return; + } + + QScopedPointer manager( + new OrgFreedesktopDBusObjectManagerInterface( + QStringLiteral("org.bluez"), QStringLiteral("/"), + QDBusConnection::systemBus())); + + QDBusPendingReply reply = manager->GetManagedObjects(); + reply.waitForFinished(); + if (reply.isError()) { + qCWarning(QT_BT_BLUEZ) << "Cannot enumerate Bluetooth devices for GATT connect"; + setError(QLowEnergyController::ConnectionError); + return; + } + + QString devicePath; + ManagedObjectList managedObjectList = reply.value(); + for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) { + const InterfaceList &ifaceList = it.value(); + + for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) { + const QString &iface = jt.key(); + const QVariantMap &ifaceValues = jt.value(); + + if (iface == QStringLiteral("org.bluez.Device1")) { + if (remoteDevice.toString() == ifaceValues.value(QStringLiteral("Address")).toString()) { + devicePath = it.key().path(); + break; + } + } + } + + if (!devicePath.isEmpty()) + break; + } + + if (devicePath.isEmpty()) { + qCDebug(QT_BT_BLUEZ) << "Cannot find targeted remote device. " + "Re-running device discovery might help"; + setError(QLowEnergyController::UnknownRemoteDeviceError); + return; + } + + managerBluez = manager.take(); + connect(managerBluez, &OrgFreedesktopDBusObjectManagerInterface::InterfacesRemoved, + this, &QLowEnergyControllerPrivateBluezDBus::interfacesRemoved); + adapter = new OrgBluezAdapter1Interface( + QStringLiteral("org.bluez"), hostAdapterPath, + QDBusConnection::systemBus(), this); + device = new OrgBluezDevice1Interface( + QStringLiteral("org.bluez"), devicePath, + QDBusConnection::systemBus(), this); + deviceMonitor = new OrgFreedesktopDBusPropertiesInterface( + QStringLiteral("org.bluez"), devicePath, + QDBusConnection::systemBus(), this); + connect(deviceMonitor, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, + this, &QLowEnergyControllerPrivateBluezDBus::devicePropertiesChanged); +} + void QLowEnergyControllerPrivateBluezDBus::connectToDevice() { - qWarning() << "QLowEnergyControllerPrivateBluezDBus::connectToDevice(): Not implemented"; - //setError(QLowEnergyController::UnknownError); + qCDebug(QT_BT_BLUEZ) << "QLowEnergyControllerPrivateBluezDBus::connectToDevice()"; + + connectToDeviceHelper(); + + if (!adapter || !device) + return; + + if (!adapter->powered()) { + qCWarning(QT_BT_BLUEZ) << "Error: Local adapter is powered off"; + setError(QLowEnergyController::ConnectionError); + return; + } + + setState(QLowEnergyController::ConnectingState); + + //Bluez interface is shared among all platform processes + //and hence we might be connected already + if (device->connected() && device->servicesResolved()) { + //connectToDevice is noop + disconnectSignalRequired = true; + setState(QLowEnergyController::ConnectedState); + return; + } + + pendingConnect = true; + + QDBusPendingReply<> reply = device->Connect(); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, + [=](QDBusPendingCallWatcher* call) { + QDBusPendingReply<> reply = *call; + if (reply.isError()) { + qCDebug(QT_BT_BLUEZ) << "BTLE_DBUS::connect() failed" + << reply.reply().errorName() + << reply.reply().errorMessage(); + bool emitDisconnect = disconnectSignalRequired; + resetController(); + setError(QLowEnergyController::UnknownError); + setState(QLowEnergyController::UnconnectedState); + if (emitDisconnect) { + Q_Q(QLowEnergyController); + emit q->disconnected(); + } + } // else -> connected when Connected property is set to true (see devicePropertiesChanged()) + call->deleteLater(); + }); } void QLowEnergyControllerPrivateBluezDBus::disconnectFromDevice() { + setState(QLowEnergyController::ClosingState); + + pendingDisconnect = true; + QDBusPendingReply<> reply = device->Disconnect(); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, + [=](QDBusPendingCallWatcher* call) { + QDBusPendingReply<> reply = *call; + if (reply.isError()) { + qCDebug(QT_BT_BLUEZ) << "BTLE_DBUS::disconnect() failed" + << reply.reply().errorName() + << reply.reply().errorMessage(); + bool emitDisconnect = disconnectSignalRequired; + resetController(); + setState(QLowEnergyController::UnconnectedState); + if (emitDisconnect) { + Q_Q(QLowEnergyController); + emit q->disconnected(); + } + } + call->deleteLater(); + }); } void QLowEnergyControllerPrivateBluezDBus::discoverServices() -- cgit v1.2.3 From 4a008da8dd7acec3ef45392e024e31552dbc3c49 Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Fri, 8 Dec 2017 16:25:28 +0100 Subject: Support service discovery for BlueZ DBus backend At the same time a typo in a comment is fixed. Task-number: QTBUG-46819 Change-Id: Ic017df4d2d52cf0d226b6e083a9ec5f28657dee0 Reviewed-by: Oliver Wolff --- src/bluetooth/qlowenergycontroller_bluezdbus.cpp | 48 ++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'src/bluetooth/qlowenergycontroller_bluezdbus.cpp') diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp index 1829f153..1464561c 100644 --- a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp +++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp @@ -41,6 +41,7 @@ #include "bluez/adapter1_bluez5_p.h" #include "bluez/bluez5_helper_p.h" #include "bluez/device1_bluez5_p.h" +#include "bluez/gattservice1_p.h" #include "bluez/objectmanager_p.h" #include "bluez/properties_p.h" @@ -308,7 +309,54 @@ void QLowEnergyControllerPrivateBluezDBus::disconnectFromDevice() void QLowEnergyControllerPrivateBluezDBus::discoverServices() { + QDBusPendingReply reply = managerBluez->GetManagedObjects(); + reply.waitForFinished(); + if (reply.isError()) { + qCWarning(QT_BT_BLUEZ) << "Cannot discover services"; + setError(QLowEnergyController::UnknownError); + setState(QLowEnergyController::DiscoveredState); + return; + } + + Q_Q(QLowEnergyController); + + const ManagedObjectList managedObjectList = reply.value(); + const QString servicePathPrefix = device->path().append(QStringLiteral("/service")); + for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) { + const InterfaceList &ifaceList = it.value(); + if (!it.key().path().startsWith(servicePathPrefix)) + continue; + + for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) { + const QString &iface = jt.key(); + + if (iface == QStringLiteral("org.bluez.GattService1")) { + QScopedPointer service(new OrgBluezGattService1Interface( + QStringLiteral("org.bluez"),it.key().path(), + QDBusConnection::systemBus(), this)); + + QSharedPointer priv = QSharedPointer::create(); + priv->uuid = QBluetoothUuid(service->uUID()); + + //no handles available + //priv->startHandle = start; + //priv->endHandle = end; + service->primary() + ? priv->type = QLowEnergyService::PrimaryService + : priv->type = QLowEnergyService::IncludedService; + priv->setController(this); + + serviceList.insert(priv->uuid, priv); + //TODO enable once discoverServiceDetails() is implemented + //foundServices.insert(priv->uuid, service); + + emit q->serviceDiscovered(priv->uuid); + } + } + } + setState(QLowEnergyController::DiscoveredState); + emit q->discoveryFinished(); } void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoothUuid &/*service*/) -- cgit v1.2.3 From 4462750a68cb93b5d363b6efdbe1d6a6f0e5811f Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Thu, 14 Dec 2017 14:29:53 +0100 Subject: Implement QLEService::discoverDetails for DBus Bluez This includes support for reading of characteristics and descriptors. Task-number: QTBUG-46819 Change-Id: I8ac1ef3bcd5bc475941f1a9f889d0742eb8def35 Reviewed-by: Oliver Wolff --- src/bluetooth/qlowenergycontroller_bluezdbus.cpp | 364 ++++++++++++++++++++++- 1 file changed, 357 insertions(+), 7 deletions(-) (limited to 'src/bluetooth/qlowenergycontroller_bluezdbus.cpp') diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp index 1464561c..b398def7 100644 --- a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp +++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp @@ -42,6 +42,9 @@ #include "bluez/bluez5_helper_p.h" #include "bluez/device1_bluez5_p.h" #include "bluez/gattservice1_p.h" + +#include "bluez/gattchar1_p.h" +#include "bluez/gattdesc1_p.h" #include "bluez/objectmanager_p.h" #include "bluez/properties_p.h" @@ -158,7 +161,12 @@ void QLowEnergyControllerPrivateBluezDBus::resetController() deviceMonitor = nullptr; } + dbusServices.clear(); + jobs.clear(); + invalidateServices(); + pendingConnect = pendingDisconnect = disconnectSignalRequired = false; + jobPending = false; } void QLowEnergyControllerPrivateBluezDBus::connectToDeviceHelper() @@ -337,18 +345,16 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServices() QSharedPointer priv = QSharedPointer::create(); priv->uuid = QBluetoothUuid(service->uUID()); - - //no handles available - //priv->startHandle = start; - //priv->endHandle = end; service->primary() ? priv->type = QLowEnergyService::PrimaryService : priv->type = QLowEnergyService::IncludedService; priv->setController(this); + GattService serviceContainer; + serviceContainer.servicePath = it.key().path(); + serviceList.insert(priv->uuid, priv); - //TODO enable once discoverServiceDetails() is implemented - //foundServices.insert(priv->uuid, service); + dbusServices.insert(priv->uuid, serviceContainer); emit q->serviceDiscovered(priv->uuid); } @@ -359,9 +365,353 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServices() emit q->discoveryFinished(); } -void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoothUuid &/*service*/) +void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoothUuid &service) +{ + if (!serviceList.contains(service) || !dbusServices.contains(service)) { + qCWarning(QT_BT_BLUEZ) << "Discovery of unknown service" << service.toString() + << "not possible"; + return; + } + + //clear existing service data and run new discovery + QSharedPointer serviceData = serviceList.value(service); + serviceData->characteristicList.clear(); + + GattService &dbusData = dbusServices[service]; + dbusData.characteristics.clear(); + + QDBusPendingReply reply = managerBluez->GetManagedObjects(); + reply.waitForFinished(); + if (reply.isError()) { + qCWarning(QT_BT_BLUEZ) << "Cannot discover services"; + setError(QLowEnergyController::UnknownError); + setState(QLowEnergyController::DiscoveredState); + return; + } + + QStringList descriptorPaths; + const ManagedObjectList managedObjectList = reply.value(); + for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) { + const InterfaceList &ifaceList = it.value(); + if (!it.key().path().startsWith(dbusData.servicePath)) + continue; + + for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) { + const QString &iface = jt.key(); + if (iface == QStringLiteral("org.bluez.GattCharacteristic1")) { + auto charInterface = QSharedPointer::create( + QStringLiteral("org.bluez"), it.key().path(), + QDBusConnection::systemBus()); + GattCharacteristic dbusCharData; + dbusCharData.characteristic = charInterface; + dbusData.characteristics.append(dbusCharData); + } else if (iface == QStringLiteral("org.bluez.GattDescriptor1")) { + auto descInterface = QSharedPointer::create( + QStringLiteral("org.bluez"), it.key().path(), + QDBusConnection::systemBus()); + bool found = false; + for (GattCharacteristic &dbusCharData : dbusData.characteristics) { + if (!descInterface->path().startsWith( + dbusCharData.characteristic->path())) + continue; + + found = true; + dbusCharData.descriptors.append(descInterface); + break; + } + + Q_ASSERT(found); + if (!found) + qCWarning(QT_BT_BLUEZ) << "Descriptor discovery error"; + } + } + } + + //populate servicePrivate based on dbus data + serviceData->startHandle = runningHandle++; + for (const GattCharacteristic &dbusChar : qAsConst(dbusData.characteristics)) { + const QLowEnergyHandle indexHandle = runningHandle++; + QLowEnergyServicePrivate::CharData charData; + + // characteristic data + charData.valueHandle = runningHandle++; + const QStringList properties = dbusChar.characteristic->flags(); + + for (const auto &entry : properties) { + if (entry == QStringLiteral("broadcast")) + charData.properties.setFlag(QLowEnergyCharacteristic::Broadcasting, true); + else if (entry == QStringLiteral("read")) + charData.properties.setFlag(QLowEnergyCharacteristic::Read, true); + else if (entry == QStringLiteral("write-without-response")) + charData.properties.setFlag(QLowEnergyCharacteristic::WriteNoResponse, true); + else if (entry == QStringLiteral("write")) + charData.properties.setFlag(QLowEnergyCharacteristic::Write, true); + else if (entry == QStringLiteral("notify")) + charData.properties.setFlag(QLowEnergyCharacteristic::Notify, true); + else if (entry == QStringLiteral("indicate")) + charData.properties.setFlag(QLowEnergyCharacteristic::Indicate, true); + else if (entry == QStringLiteral("authenticated-signed-writes")) + charData.properties.setFlag(QLowEnergyCharacteristic::WriteSigned, true); + else if (entry == QStringLiteral("reliable-write")) + charData.properties.setFlag(QLowEnergyCharacteristic::ExtendedProperty, true); + else if (entry == QStringLiteral("writable-auxiliaries")) + charData.properties.setFlag(QLowEnergyCharacteristic::ExtendedProperty, true); + //all others ignored - not relevant for this API + } + + charData.uuid = QBluetoothUuid(dbusChar.characteristic->uUID()); + + // schedule read for initial char value + if (charData.properties.testFlag(QLowEnergyCharacteristic::Read)) { + GattJob job; + job.flags = GattJob::JobFlags({GattJob::CharRead, GattJob::ServiceDiscovery}); + job.service = serviceData; + job.handle = indexHandle; + jobs.append(job); + } + + // descriptor data + for (const auto &descEntry : qAsConst(dbusChar.descriptors)) { + const QLowEnergyHandle descriptorHandle = runningHandle++; + QLowEnergyServicePrivate::DescData descData; + descData.uuid = QBluetoothUuid(descEntry->uUID()); + charData.descriptorList.insert(descriptorHandle, descData); + + // 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; + } + + serviceData->endHandle = runningHandle++; + + // last job is last step of service discovery + GattJob &lastJob = jobs.last(); + lastJob.flags.setFlag(GattJob::LastServiceDiscovery, true); + + scheduleNextJob(); +} + +void QLowEnergyControllerPrivateBluezDBus::prepareNextJob() +{ + jobs.takeFirst(); // finish last job + jobPending = false; + + scheduleNextJob(); // continue with next job - if available +} + +void QLowEnergyControllerPrivateBluezDBus::onCharReadFinished(QDBusPendingCallWatcher *call) { + Q_ASSERT(jobPending); + Q_ASSERT(!jobs.isEmpty()); + + const GattJob nextJob = jobs.constFirst(); + Q_ASSERT(nextJob.flags.testFlag(GattJob::CharRead)); + QSharedPointer service = serviceForHandle(nextJob.handle); + if (service.isNull() || !dbusServices.contains(service->uuid)) { + qCWarning(QT_BT_BLUEZ) << "onCharReadFinished: Invalid GATT job. Skipping."; + call->deleteLater(); + prepareNextJob(); + return; + } + const QLowEnergyServicePrivate::CharData &charData = + service->characteristicList.value(nextJob.handle); + + bool isServiceDiscovery = nextJob.flags.testFlag(GattJob::ServiceDiscovery); + QDBusPendingReply reply = *call; + if (reply.isError()) { + qCWarning(QT_BT_BLUEZ) << "Cannot initiate reading of" << charData.uuid + << "of service" << service->uuid + << reply.error().name() << reply.error().message(); + if (!isServiceDiscovery) + service->setError(QLowEnergyService::CharacteristicReadError); + } else { + qCDebug(QT_BT_BLUEZ) << "Read Char:" << charData.uuid << reply.value().toHex(); + updateValueOfCharacteristic(nextJob.handle, reply.value(), false); + + if (isServiceDiscovery) { + if (nextJob.flags.testFlag(GattJob::LastServiceDiscovery)) + service->setState(QLowEnergyService::ServiceDiscovered); + } else { + QLowEnergyCharacteristic ch(service, nextJob.handle); + emit service->characteristicRead(ch, reply.value()); + } + } + + call->deleteLater(); + prepareNextJob(); +} + + +void QLowEnergyControllerPrivateBluezDBus::onDescReadFinished(QDBusPendingCallWatcher *call) +{ + Q_ASSERT(jobPending); + Q_ASSERT(!jobs.isEmpty()); + + const GattJob nextJob = jobs.constFirst(); + Q_ASSERT(nextJob.flags.testFlag(GattJob::DescRead)); + + QSharedPointer service = serviceForHandle(nextJob.handle); + if (service.isNull() || !dbusServices.contains(service->uuid)) { + qCWarning(QT_BT_BLUEZ) << "onDescReadFinished: Invalid GATT job. Skipping."; + call->deleteLater(); + prepareNextJob(); + return; + } + + QLowEnergyCharacteristic ch = characteristicForHandle(nextJob.handle); + if (!ch.isValid()) { + qCWarning(QT_BT_BLUEZ) << "Cannot find char for desc read (onDescReadFinished 1)."; + call->deleteLater(); + prepareNextJob(); + return; + } + + const QLowEnergyServicePrivate::CharData &charData = + service->characteristicList.value(ch.attributeHandle()); + + if (!charData.descriptorList.contains(nextJob.handle)) { + qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor (onDescReadFinished 2)."; + call->deleteLater(); + prepareNextJob(); + return; + } + + bool isServiceDiscovery = nextJob.flags.testFlag(GattJob::ServiceDiscovery); + const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid; + + QDBusPendingReply reply = *call; + if (reply.isError()) { + qCWarning(QT_BT_BLUEZ) << "Cannot read descriptor (onDescReadFinished 3): " + << charData.descriptorList[nextJob.handle].uuid + << charData.uuid + << reply.error().name() << reply.error().message(); + if (!isServiceDiscovery) + service->setError(QLowEnergyService::DescriptorReadError); + } else { + qCDebug(QT_BT_BLUEZ) << "Read Desc:" << reply.value(); + updateValueOfDescriptor(ch.attributeHandle(), nextJob.handle, reply.value(), false); + + if (isServiceDiscovery) { + if (nextJob.flags.testFlag(GattJob::LastServiceDiscovery)) + service->setState(QLowEnergyService::ServiceDiscovered); + } else { + QLowEnergyDescriptor desc(service, ch.attributeHandle(), nextJob.handle); + emit service->descriptorRead(desc, reply.value()); + } + } + + call->deleteLater(); + prepareNextJob(); +} + +void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob() +{ + if (jobPending || jobs.isEmpty()) + return; + + jobPending = true; + + const GattJob nextJob = jobs.constFirst(); + QSharedPointer service = serviceForHandle(nextJob.handle); + if (service.isNull() || !dbusServices.contains(service->uuid)) { + qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleReadChar). Skipping."; + prepareNextJob(); + return; + } + + const GattService &dbusServiceData = dbusServices[service->uuid]; + + if (nextJob.flags.testFlag(GattJob::CharRead)) { + // characteristic reading *************************************** + if (!service->characteristicList.contains(nextJob.handle)) { + qCWarning(QT_BT_BLUEZ) << "Invalid Char handle when reading. Skipping."; + prepareNextJob(); + return; + } + + const QLowEnergyServicePrivate::CharData &charData = + service->characteristicList.value(nextJob.handle); + bool foundChar = false; + for (const auto &gattChar : qAsConst(dbusServiceData.characteristics)) { + if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID())) + continue; + + QDBusPendingReply reply = gattChar.characteristic->ReadValue(QVariantMap()); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, + this, &QLowEnergyControllerPrivateBluezDBus::onCharReadFinished); + + foundChar = true; + break; + } + + if (!foundChar) { + qCWarning(QT_BT_BLUEZ) << "Cannot find char for reading. Skipping."; + prepareNextJob(); + return; + } + } else if (nextJob.flags.testFlag(GattJob::CharWrite)) { + //TODO implement CharWrite *************************************** + } else if (nextJob.flags.testFlag(GattJob::DescRead)) { + // descriptor reading *************************************** + QLowEnergyCharacteristic ch = characteristicForHandle(nextJob.handle); + if (!ch.isValid()) { + qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleReadDesc 1). Skipping."; + prepareNextJob(); + return; + } + + const QLowEnergyServicePrivate::CharData &charData = + service->characteristicList.value(ch.attributeHandle()); + qCDebug(QT_BT_BLUEZ) << "###########" << ch.handle() << nextJob.handle + << charData.descriptorList.keys() + << service->characteristicList.keys(); + if (!charData.descriptorList.contains(nextJob.handle)) { + qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleReadDesc 2). Skipping."; + prepareNextJob(); + return; + } + + const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid; + bool foundDesc = false; + for (const auto &gattChar : qAsConst(dbusServiceData.characteristics)) { + if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID())) + continue; + + for (const auto &gattDesc : qAsConst(gattChar.descriptors)) { + if (descUuid != QBluetoothUuid(gattDesc->uUID())) + continue; + + QDBusPendingReply reply = gattDesc->ReadValue(QVariantMap()); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, + this, &QLowEnergyControllerPrivateBluezDBus::onDescReadFinished); + foundDesc = true; + break; + } + + if (foundDesc) + break; + } + + if (!foundDesc) { + qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor for reading. Skipping."; + prepareNextJob(); + return; + } + } else if (nextJob.flags.testFlag(GattJob::DescWrite)) { + //TODO implement DescWrite *************************************** + } else { + qCWarning(QT_BT_BLUEZ) << "Unknown gatt job type. Skipping."; + prepareNextJob(); + } } void QLowEnergyControllerPrivateBluezDBus::readCharacteristic( -- cgit v1.2.3 From 1e03015803549e2865b2772fe5be43d7bec8ccc1 Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Thu, 4 Jan 2018 13:08:29 +0100 Subject: Implement Char read & write for dbus Gatt Read/write is limited to central role support. Peripheral role support will follow at a later stage. Task-number: QTBUG-46819 Change-Id: Idc27ea31cf0629470dc46490235c57f64b51498d Reviewed-by: Oliver Wolff --- src/bluetooth/qlowenergycontroller_bluezdbus.cpp | 122 +++++++++++++++++++++-- 1 file changed, 114 insertions(+), 8 deletions(-) (limited to 'src/bluetooth/qlowenergycontroller_bluezdbus.cpp') diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp index b398def7..3f3bcbaa 100644 --- a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp +++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp @@ -611,6 +611,43 @@ void QLowEnergyControllerPrivateBluezDBus::onDescReadFinished(QDBusPendingCallWa prepareNextJob(); } +void QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished(QDBusPendingCallWatcher *call) +{ + Q_ASSERT(jobPending); + Q_ASSERT(!jobs.isEmpty()); + + const GattJob nextJob = jobs.constFirst(); + Q_ASSERT(nextJob.flags.testFlag(GattJob::CharWrite)); + + QSharedPointer service = nextJob.service; + if (!dbusServices.contains(service->uuid)) { + qCWarning(QT_BT_BLUEZ) << "onCharWriteFinished: Invalid GATT job. Skipping."; + call->deleteLater(); + prepareNextJob(); + return; + } + + const QLowEnergyServicePrivate::CharData &charData = + service->characteristicList.value(nextJob.handle); + + QDBusPendingReply<> reply = *call; + if (reply.isError()) { + qCWarning(QT_BT_BLUEZ) << "Cannot initiate writing of" << charData.uuid + << "of service" << service->uuid + << reply.error().name() << reply.error().message(); + service->setError(QLowEnergyService::CharacteristicWriteError); + } else { + qCDebug(QT_BT_BLUEZ) << "Write Char:" << charData.uuid << nextJob.value.toHex(); + updateValueOfCharacteristic(nextJob.handle, nextJob.value, false); + + QLowEnergyCharacteristic ch(service, nextJob.handle); + emit service->characteristicWritten(ch, nextJob.value); + } + + call->deleteLater(); + prepareNextJob(); +} + void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob() { if (jobPending || jobs.isEmpty()) @@ -658,7 +695,34 @@ void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob() return; } } else if (nextJob.flags.testFlag(GattJob::CharWrite)) { - //TODO implement CharWrite *************************************** + // characteristic writing *************************************** + if (!service->characteristicList.contains(nextJob.handle)) { + qCWarning(QT_BT_BLUEZ) << "Invalid Char handle when writing. Skipping."; + prepareNextJob(); + return; + } + + const QLowEnergyServicePrivate::CharData &charData = + service->characteristicList.value(nextJob.handle); + bool foundChar = false; + for (const auto &gattChar : qAsConst(dbusServiceData.characteristics)) { + if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID())) + continue; + + QDBusPendingReply<> reply = gattChar.characteristic->WriteValue(nextJob.value, QVariantMap()); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, + this, &QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished); + + foundChar = true; + break; + } + + if (!foundChar) { + qCWarning(QT_BT_BLUEZ) << "Cannot find char for writing. Skipping."; + prepareNextJob(); + return; + } } else if (nextJob.flags.testFlag(GattJob::DescRead)) { // descriptor reading *************************************** QLowEnergyCharacteristic ch = characteristicForHandle(nextJob.handle); @@ -715,10 +779,32 @@ void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob() } void QLowEnergyControllerPrivateBluezDBus::readCharacteristic( - const QSharedPointer /*service*/, - const QLowEnergyHandle /*charHandle*/) + const QSharedPointer service, + const QLowEnergyHandle charHandle) { + Q_ASSERT(!service.isNull()); + if (!service->characteristicList.contains(charHandle)) { + qCWarning(QT_BT_BLUEZ) << "Read characteristic does not belong to service" + << service->uuid; + return; + } + + const QLowEnergyServicePrivate::CharData &charDetails + = service->characteristicList[charHandle]; + if (!(charDetails.properties & QLowEnergyCharacteristic::Read)) { + // if this succeeds the device has a bug, char is advertised as + // non-readable. We try to be permissive and let the remote + // device answer to the read attempt + qCWarning(QT_BT_BLUEZ) << "Reading non-readable char" << charHandle; + } + + GattJob job; + job.flags = GattJob::JobFlags({GattJob::CharRead}); + job.service = service; + job.handle = charHandle; + jobs.append(job); + scheduleNextJob(); } void QLowEnergyControllerPrivateBluezDBus::readDescriptor( @@ -730,12 +816,32 @@ void QLowEnergyControllerPrivateBluezDBus::readDescriptor( } void QLowEnergyControllerPrivateBluezDBus::writeCharacteristic( - const QSharedPointer /*service*/, - const QLowEnergyHandle /*charHandle*/, - const QByteArray &/*newValue*/, - QLowEnergyService::WriteMode /*writeMode*/) - { + const QSharedPointer service, + const QLowEnergyHandle charHandle, + const QByteArray &newValue, + QLowEnergyService::WriteMode writeMode) +{ + Q_ASSERT(!service.isNull()); + if (!service->characteristicList.contains(charHandle)) { + qCWarning(QT_BT_BLUEZ) << "Write characteristic does not belong to service" + << service->uuid; + return; + } + if (role == QLowEnergyController::CentralRole) { + GattJob job; + job.flags = GattJob::JobFlags({GattJob::CharWrite}); + job.service = service; + job.handle = charHandle; + job.value = newValue; + job.writeMode = writeMode; + jobs.append(job); + + scheduleNextJob(); + } else { + qWarning(QT_BT_BLUEZ) << "writeCharacteristic() not implemented for DBus Bluez GATT"; + service->setError(QLowEnergyService::CharacteristicWriteError); + } } void QLowEnergyControllerPrivateBluezDBus::writeDescriptor( -- cgit v1.2.3 From 45ea69635f08f967b31660e2bcd33d3bb8187497 Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Wed, 10 Jan 2018 10:31:41 +0100 Subject: Implement read-/writeDescriptor() for DBus GATT In addition there is a minor debug cleanup in the reading of descriptor code. Task-number: QTBUG-46819 Change-Id: I3f65c7d113b306b5b4892fa5df189476c06df0e9 Reviewed-by: Oliver Wolff --- src/bluetooth/qlowenergycontroller_bluezdbus.cpp | 137 +++++++++++++++++++++-- 1 file changed, 125 insertions(+), 12 deletions(-) (limited to 'src/bluetooth/qlowenergycontroller_bluezdbus.cpp') diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp index 3f3bcbaa..6cbe67bb 100644 --- a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp +++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp @@ -548,7 +548,6 @@ void QLowEnergyControllerPrivateBluezDBus::onCharReadFinished(QDBusPendingCallWa prepareNextJob(); } - void QLowEnergyControllerPrivateBluezDBus::onDescReadFinished(QDBusPendingCallWatcher *call) { Q_ASSERT(jobPending); @@ -648,6 +647,50 @@ void QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished(QDBusPendingCallW prepareNextJob(); } +void QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished(QDBusPendingCallWatcher *call) +{ + Q_ASSERT(jobPending); + Q_ASSERT(!jobs.isEmpty()); + + const GattJob nextJob = jobs.constFirst(); + Q_ASSERT(nextJob.flags.testFlag(GattJob::DescWrite)); + + QSharedPointer service = nextJob.service; + if (!dbusServices.contains(service->uuid)) { + qCWarning(QT_BT_BLUEZ) << "onDescWriteFinished: Invalid GATT job. Skipping."; + call->deleteLater(); + prepareNextJob(); + return; + } + + const QLowEnergyCharacteristic associatedChar = characteristicForHandle(nextJob.handle); + const QLowEnergyDescriptor descriptor = descriptorForHandle(nextJob.handle); + if (!associatedChar.isValid() || !descriptor.isValid()) { + qCWarning(QT_BT_BLUEZ) << "onDescWriteFinished: Cannot find associated char/desc: " + << associatedChar.isValid(); + call->deleteLater(); + prepareNextJob(); + return; + } + + QDBusPendingReply<> reply = *call; + if (reply.isError()) { + qCWarning(QT_BT_BLUEZ) << "Cannot initiate writing of" << descriptor.uuid() + << "of char" << associatedChar.uuid() + << "of service" << service->uuid + << reply.error().name() << reply.error().message(); + service->setError(QLowEnergyService::DescriptorWriteError); + } else { + qCDebug(QT_BT_BLUEZ) << "Write Desc:" << descriptor.uuid() << nextJob.value.toHex(); + updateValueOfDescriptor(associatedChar.attributeHandle(), nextJob.handle, + nextJob.value, false); + emit service->descriptorWritten(descriptor, nextJob.value); + } + + call->deleteLater(); + prepareNextJob(); +} + void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob() { if (jobPending || jobs.isEmpty()) @@ -734,9 +777,6 @@ void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob() const QLowEnergyServicePrivate::CharData &charData = service->characteristicList.value(ch.attributeHandle()); - qCDebug(QT_BT_BLUEZ) << "###########" << ch.handle() << nextJob.handle - << charData.descriptorList.keys() - << service->characteristicList.keys(); if (!charData.descriptorList.contains(nextJob.handle)) { qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleReadDesc 2). Skipping."; prepareNextJob(); @@ -771,7 +811,49 @@ void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob() return; } } else if (nextJob.flags.testFlag(GattJob::DescWrite)) { - //TODO implement DescWrite *************************************** + // descriptor writing *************************************** + const QLowEnergyCharacteristic ch = characteristicForHandle(nextJob.handle); + if (!ch.isValid()) { + qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleWriteDesc 1). Skipping."; + prepareNextJob(); + return; + } + + const QLowEnergyServicePrivate::CharData &charData = + service->characteristicList.value(ch.attributeHandle()); + if (!charData.descriptorList.contains(nextJob.handle)) { + qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleWriteDesc 2). Skipping."; + prepareNextJob(); + return; + } + + const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid; + bool foundDesc = false; + for (const auto &gattChar : qAsConst(dbusServiceData.characteristics)) { + if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID())) + continue; + + for (const auto &gattDesc : qAsConst(gattChar.descriptors)) { + if (descUuid != QBluetoothUuid(gattDesc->uUID())) + continue; + + QDBusPendingReply<> reply = gattDesc->WriteValue(nextJob.value, QVariantMap()); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, + this, &QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished); + foundDesc = true; + break; + } + + if (foundDesc) + break; + } + + if (!foundDesc) { + qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor for writing. Skipping."; + prepareNextJob(); + return; + } } else { qCWarning(QT_BT_BLUEZ) << "Unknown gatt job type. Skipping."; prepareNextJob(); @@ -808,11 +890,26 @@ void QLowEnergyControllerPrivateBluezDBus::readCharacteristic( } void QLowEnergyControllerPrivateBluezDBus::readDescriptor( - const QSharedPointer /*service*/, - const QLowEnergyHandle /*charHandle*/, - const QLowEnergyHandle /*descriptorHandle*/) + const QSharedPointer service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle) { + Q_ASSERT(!service.isNull()); + if (!service->characteristicList.contains(charHandle)) + return; + const QLowEnergyServicePrivate::CharData &charDetails + = service->characteristicList[charHandle]; + if (!charDetails.descriptorList.contains(descriptorHandle)) + return; + + GattJob job; + job.flags = GattJob::JobFlags({GattJob::DescRead}); + job.service = service; + job.handle = descriptorHandle; + jobs.append(job); + + scheduleNextJob(); } void QLowEnergyControllerPrivateBluezDBus::writeCharacteristic( @@ -845,12 +942,28 @@ void QLowEnergyControllerPrivateBluezDBus::writeCharacteristic( } void QLowEnergyControllerPrivateBluezDBus::writeDescriptor( - const QSharedPointer /*service*/, - const QLowEnergyHandle /*charHandle*/, - const QLowEnergyHandle /*descriptorHandle*/, - const QByteArray &/*newValue*/) + const QSharedPointer service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle, + const QByteArray &newValue) { + Q_ASSERT(!service.isNull()); + if (!service->characteristicList.contains(charHandle)) + return; + if (role == QLowEnergyController::CentralRole) { + GattJob job; + job.flags = GattJob::JobFlags({GattJob::DescWrite}); + job.service = service; + job.handle = descriptorHandle; + job.value = newValue; + jobs.append(job); + + scheduleNextJob(); + } else { + qWarning(QT_BT_BLUEZ) << "writeDescriptor() peripheral not implemented for DBus Bluez GATT"; + service->setError(QLowEnergyService::CharacteristicWriteError); + } } void QLowEnergyControllerPrivateBluezDBus::startAdvertising( -- cgit v1.2.3 From e55981842b702dced8ed0301ff9550e7d98e8a14 Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Mon, 22 Jan 2018 12:24:01 +0100 Subject: BlueZ: Implements characteristicChanged() notifications This feature depends on ClientCharacteristicConfiguration descriptor types which determine whether changed signals are sent or not. The patch also ensures that cached char values are not updated/cached when the char is not readable. Task-number: QTBUG-46819 Change-Id: I841bcaaca60c588eae7d4067b6ead6af28f957e3 Reviewed-by: Oliver Wolff --- src/bluetooth/qlowenergycontroller_bluezdbus.cpp | 88 +++++++++++++++++++++--- 1 file changed, 79 insertions(+), 9 deletions(-) (limited to 'src/bluetooth/qlowenergycontroller_bluezdbus.cpp') diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp index 6cbe67bb..e9087642 100644 --- a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp +++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp @@ -123,6 +123,35 @@ void QLowEnergyControllerPrivateBluezDBus::devicePropertiesChanged( } } +void QLowEnergyControllerPrivateBluezDBus::characteristicPropertiesChanged( + QLowEnergyHandle charHandle, const QString &interface, + const QVariantMap &changedProperties, + const QStringList &/*removedProperties*/) +{ + //qCDebug(QT_BT_BLUEZ) << "$$$$$$$$$$$$$$$$$$ char monitor" + // << interface << changedProperties << charHandle; + if (interface != QStringLiteral("org.bluez.GattCharacteristic1")) + return; + + if (!changedProperties.contains(QStringLiteral("Value"))) + return; + + const QLowEnergyCharacteristic changedChar = characteristicForHandle(charHandle); + const QLowEnergyDescriptor ccnDescriptor = changedChar.descriptor( + QBluetoothUuid::ClientCharacteristicConfiguration); + if (!ccnDescriptor.isValid()) + return; + + const QByteArray newValue = changedProperties.value(QStringLiteral("Value")).toByteArray(); + if (changedChar.properties() & QLowEnergyCharacteristic::Read) + updateValueOfCharacteristic(charHandle, newValue, false); //TODO upgrade to NEW_VALUE/APPEND_VALUE + + auto service = serviceForHandle(charHandle); + + if (!service.isNull()) + emit service->characteristicChanged(changedChar, newValue); +} + void QLowEnergyControllerPrivateBluezDBus::interfacesRemoved( const QDBusObjectPath &objectPath, const QStringList &/*interfaces*/) { @@ -429,7 +458,7 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoo //populate servicePrivate based on dbus data serviceData->startHandle = runningHandle++; - for (const GattCharacteristic &dbusChar : qAsConst(dbusData.characteristics)) { + for (GattCharacteristic &dbusChar : dbusData.characteristics) { const QLowEnergyHandle indexHandle = runningHandle++; QLowEnergyServicePrivate::CharData charData; @@ -477,6 +506,23 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoo descData.uuid = QBluetoothUuid(descEntry->uUID()); charData.descriptorList.insert(descriptorHandle, descData); + + // every ClientCharacteristicConfiguration needs to track property changes + if (descData.uuid + == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { + dbusChar.charMonitor = QSharedPointer::create( + QStringLiteral("org.bluez"), + dbusChar.characteristic->path(), + QDBusConnection::systemBus(), this); + connect(dbusChar.charMonitor.data(), &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, + this, [this, indexHandle](const QString &interface, const QVariantMap &changedProperties, + const QStringList &removedProperties) { + + characteristicPropertiesChanged(indexHandle, interface, + changedProperties, removedProperties); + }); + } + // schedule read for initial descriptor value GattJob job; job.flags = GattJob::JobFlags({GattJob::DescRead, GattJob::ServiceDiscovery}); @@ -533,7 +579,8 @@ void QLowEnergyControllerPrivateBluezDBus::onCharReadFinished(QDBusPendingCallWa service->setError(QLowEnergyService::CharacteristicReadError); } else { qCDebug(QT_BT_BLUEZ) << "Read Char:" << charData.uuid << reply.value().toHex(); - updateValueOfCharacteristic(nextJob.handle, reply.value(), false); + if (charData.properties.testFlag(QLowEnergyCharacteristic::Read)) + updateValueOfCharacteristic(nextJob.handle, reply.value(), false); if (isServiceDiscovery) { if (nextJob.flags.testFlag(GattJob::LastServiceDiscovery)) @@ -636,11 +683,15 @@ void QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished(QDBusPendingCallW << reply.error().name() << reply.error().message(); service->setError(QLowEnergyService::CharacteristicWriteError); } else { - qCDebug(QT_BT_BLUEZ) << "Write Char:" << charData.uuid << nextJob.value.toHex(); - updateValueOfCharacteristic(nextJob.handle, nextJob.value, false); + if (charData.properties.testFlag(QLowEnergyCharacteristic::Read)) + updateValueOfCharacteristic(nextJob.handle, nextJob.value, false); QLowEnergyCharacteristic ch(service, nextJob.handle); - emit service->characteristicWritten(ch, nextJob.value); + // write without respone 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); + } } call->deleteLater(); @@ -837,10 +888,29 @@ void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob() if (descUuid != QBluetoothUuid(gattDesc->uUID())) continue; - QDBusPendingReply<> reply = gattDesc->WriteValue(nextJob.value, QVariantMap()); - QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); - connect(watcher, &QDBusPendingCallWatcher::finished, - this, &QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished); + //notifications enabled via characteristics Start/StopNotify() functions + //otherwise regular WriteValue() calls on descriptor interface + if (descUuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { + const QByteArray value = nextJob.value; + + QDBusPendingReply<> reply; + qCDebug(QT_BT_BLUEZ) << "Init CCC change to" << value.toHex() + << charData.uuid << service->uuid; + if (value == QByteArray::fromHex("0100") || value == QByteArray::fromHex("0200")) + reply = gattChar.characteristic->StartNotify(); + else + reply = gattChar.characteristic->StopNotify(); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, + this, &QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished); + } else { + QDBusPendingReply<> reply = gattDesc->WriteValue(nextJob.value, QVariantMap()); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, + this, &QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished); + + } + foundDesc = true; break; } -- cgit v1.2.3 From 5c160d9fdf9d93307bcdfabef4a78eb7dc18694e Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Fri, 13 Apr 2018 17:00:19 +0200 Subject: BlueZ: Fix crash when discovering GATT service without readable items The jobs list might be empty and calling last() causes an assert in QVector. This implies the current process under discovery does not have any descriptors or characteristics which are readable. In such cases no async read requests have to be put forward and the discovery is already done. Change-Id: I8417bfcd146866cb16c295c9e9d4890270574a56 Reviewed-by: Oliver Wolff --- src/bluetooth/qlowenergycontroller_bluezdbus.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/bluetooth/qlowenergycontroller_bluezdbus.cpp') diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp index e9087642..afc4b8c1 100644 --- a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp +++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp @@ -537,8 +537,12 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoo serviceData->endHandle = runningHandle++; // last job is last step of service discovery - GattJob &lastJob = jobs.last(); - lastJob.flags.setFlag(GattJob::LastServiceDiscovery, true); + if (!jobs.isEmpty()) { + GattJob &lastJob = jobs.last(); + lastJob.flags.setFlag(GattJob::LastServiceDiscovery, true); + } else { + serviceData->setState(QLowEnergyService::ServiceDiscovered); + } scheduleNextJob(); } -- cgit v1.2.3 From 57ab030cdb245750e1043e3c5ca99c4f409cc868 Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Fri, 15 Jun 2018 13:24:36 +0200 Subject: Fix QLEController::connectToDevice() on reconnect behavior If the desired remote peripheral is already connected, the next call to connectToDevice() does not succeed. The code path to handle this situation already exists it was forgotten to emit the relevant connected() signal. This patch fixes the problem. The problem only exists on Bluez 5.42+ platforms which use the DBus central implementation. [ChangeLog][Platform Specific Changes][Linux] Fixed missing emission of QLEController::connected() upon reconnect to already connected device. This affected platforms with Bluez 5.42+ only. Task-number: QTBUG-68911 Change-Id: Ife2f3b41c33a142d6627e433cb3b141ce9b1ff8a Reviewed-by: Maurice Kalinowski Reviewed-by: Timur Pocheptsov Reviewed-by: Alex Blasche Reviewed-by: Oliver Wolff --- src/bluetooth/qlowenergycontroller_bluezdbus.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/bluetooth/qlowenergycontroller_bluezdbus.cpp') diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp index afc4b8c1..8f001f92 100644 --- a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp +++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp @@ -289,7 +289,10 @@ void QLowEnergyControllerPrivateBluezDBus::connectToDevice() if (device->connected() && device->servicesResolved()) { //connectToDevice is noop disconnectSignalRequired = true; + setState(QLowEnergyController::ConnectedState); + Q_Q(QLowEnergyController); + emit q->connected(); return; } -- cgit v1.2.3 From 9b7a4c10b67dd7d89ed30f1f94d8982dab47ded0 Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Fri, 22 Jun 2018 13:41:47 +0200 Subject: Fix crash in QLEController caused by random device disconnect This only happens with Bluez 5.42 or later versions as the DBus based GATT central client is used. The peripheral disconnects in the middle of a char or descriptor write request. The disconnect triggers the cleanup of all internal states. The write char/descriptor dbus request returns after the disconnect and executes in the context of an already reset QLowEnergyController instance. The fix ensures that the call back code does not make the assumption of operating with a connected peripheral. In fact the code had the relevant asserts already. [ChangeLog][QtBluetooth][PLatform Specific Changes] Fixed crash in DBus gatt central backend (BlueZ5) caused by device disconnecting and pending dbus call watcher returning later after disconnect. Task-number: QTBUG-68890 Change-Id: Iad9b8a1cfc8d916d49fd9b71b2d8f03b9c90639b Reviewed-by: Timur Pocheptsov --- src/bluetooth/qlowenergycontroller_bluezdbus.cpp | 32 ++++++++++++++++++------ 1 file changed, 24 insertions(+), 8 deletions(-) (limited to 'src/bluetooth/qlowenergycontroller_bluezdbus.cpp') diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp index 8f001f92..16f03405 100644 --- a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp +++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp @@ -560,8 +560,12 @@ void QLowEnergyControllerPrivateBluezDBus::prepareNextJob() void QLowEnergyControllerPrivateBluezDBus::onCharReadFinished(QDBusPendingCallWatcher *call) { - Q_ASSERT(jobPending); - Q_ASSERT(!jobs.isEmpty()); + if (!jobPending || jobs.isEmpty()) { + // this may happen when service disconnects before dbus watcher returns later on + qCWarning(QT_BT_BLUEZ) << "Aborting onCharReadFinished due to disconnect"; + Q_ASSERT(state == QLowEnergyController::UnconnectedState); + return; + } const GattJob nextJob = jobs.constFirst(); Q_ASSERT(nextJob.flags.testFlag(GattJob::CharRead)); @@ -604,8 +608,12 @@ void QLowEnergyControllerPrivateBluezDBus::onCharReadFinished(QDBusPendingCallWa void QLowEnergyControllerPrivateBluezDBus::onDescReadFinished(QDBusPendingCallWatcher *call) { - Q_ASSERT(jobPending); - Q_ASSERT(!jobs.isEmpty()); + if (!jobPending || jobs.isEmpty()) { + // this may happen when service disconnects before dbus watcher returns later on + qCWarning(QT_BT_BLUEZ) << "Aborting onDescReadFinished due to disconnect"; + Q_ASSERT(state == QLowEnergyController::UnconnectedState); + return; + } const GattJob nextJob = jobs.constFirst(); Q_ASSERT(nextJob.flags.testFlag(GattJob::DescRead)); @@ -666,8 +674,12 @@ void QLowEnergyControllerPrivateBluezDBus::onDescReadFinished(QDBusPendingCallWa void QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished(QDBusPendingCallWatcher *call) { - Q_ASSERT(jobPending); - Q_ASSERT(!jobs.isEmpty()); + if (!jobPending || jobs.isEmpty()) { + // this may happen when service disconnects before dbus watcher returns later on + qCWarning(QT_BT_BLUEZ) << "Aborting onCharWriteFinished due to disconnect"; + Q_ASSERT(state == QLowEnergyController::UnconnectedState); + return; + } const GattJob nextJob = jobs.constFirst(); Q_ASSERT(nextJob.flags.testFlag(GattJob::CharWrite)); @@ -707,8 +719,12 @@ void QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished(QDBusPendingCallW void QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished(QDBusPendingCallWatcher *call) { - Q_ASSERT(jobPending); - Q_ASSERT(!jobs.isEmpty()); + if (!jobPending || jobs.isEmpty()) { + // this may happen when service disconnects before dbus watcher returns later on + qCWarning(QT_BT_BLUEZ) << "Aborting onDescWriteFinished due to disconnect"; + Q_ASSERT(state == QLowEnergyController::UnconnectedState); + return; + } const GattJob nextJob = jobs.constFirst(); Q_ASSERT(nextJob.flags.testFlag(GattJob::DescWrite)); -- cgit v1.2.3