summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qlowenergycontroller_bluezdbus.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/qlowenergycontroller_bluezdbus.cpp')
-rw-r--r--src/bluetooth/qlowenergycontroller_bluezdbus.cpp491
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 &params,
+ 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"