summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJuha Vuolle <juha.vuolle@insta.fi>2022-11-21 11:53:27 +0200
committerJuha Vuolle <juha.vuolle@insta.fi>2022-12-07 11:47:52 +0200
commite7499c2cca615eaff3bd4c9ffe5f72d7112055cf (patch)
tree99eac7e23dffc0b0ad84b8d1c4b8c175d875531a
parenta79a8de70853f5acc9c8d9a9b10760c2418b31ac (diff)
downloadqtconnectivity-e7499c2cca615eaff3bd4c9ffe5f72d7112055cf.tar.gz
Add Bluez DBus peripheral role support
[ChangeLog][QtBluetooth] Add support for Bluez DBus peripheral role Fixes: QTBUG-107510 Change-Id: I1c26606ff0b01818c6f446147e005090582ba877 Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
-rw-r--r--src/bluetooth/CMakeLists.txt8
-rw-r--r--src/bluetooth/bluez/bluez5_helper.cpp2
-rw-r--r--src/bluetooth/bluez/bluezperipheralapplication.cpp276
-rw-r--r--src/bluetooth/bluez/bluezperipheralapplication_p.h100
-rw-r--r--src/bluetooth/bluez/bluezperipheralconnectionmanager.cpp96
-rw-r--r--src/bluetooth/bluez/bluezperipheralconnectionmanager_p.h68
-rw-r--r--src/bluetooth/bluez/bluezperipheralobjects.cpp336
-rw-r--r--src/bluetooth/bluez/bluezperipheralobjects_p.h171
-rw-r--r--src/bluetooth/bluez/leadvertisement1.cpp1
-rw-r--r--src/bluetooth/doc/src/bluetooth-index.qdoc17
-rw-r--r--src/bluetooth/qleadvertiser_bluezdbus.cpp24
-rw-r--r--src/bluetooth/qleadvertiser_bluezdbus_p.h5
-rw-r--r--src/bluetooth/qlowenergyadvertisingdata.cpp6
-rw-r--r--src/bluetooth/qlowenergyadvertisingparameters.cpp6
-rw-r--r--src/bluetooth/qlowenergycontroller.cpp34
-rw-r--r--src/bluetooth/qlowenergycontroller_bluezdbus.cpp249
-rw-r--r--src/bluetooth/qlowenergycontroller_bluezdbus_p.h22
17 files changed, 1343 insertions, 78 deletions
diff --git a/src/bluetooth/CMakeLists.txt b/src/bluetooth/CMakeLists.txt
index 3416435e..992fc21a 100644
--- a/src/bluetooth/CMakeLists.txt
+++ b/src/bluetooth/CMakeLists.txt
@@ -78,6 +78,14 @@ if(QT_FEATURE_bluez)
bluez/gattmanager1.cpp bluez/gattmanager1_p.h
bluez/leadvertisement1.cpp bluez/leadvertisement1_p.h
bluez/leadvertisingmanager1.cpp bluez/leadvertisingmanager1_p.h
+ bluez/objectmanageradaptor.cpp bluez/objectmanageradaptor_p.h
+ bluez/propertiesadaptor.cpp bluez/propertiesadaptor_p.h
+ bluez/gattcharacteristic1adaptor.cpp bluez/gattcharacteristic1adaptor_p.h
+ bluez/gattdescriptor1adaptor.cpp bluez/gattdescriptor1adaptor_p.h
+ bluez/gattservice1adaptor.cpp bluez/gattservice1adaptor_p.h
+ bluez/bluezperipheralapplication.cpp bluez/bluezperipheralapplication_p.h
+ bluez/bluezperipheralobjects.cpp bluez/bluezperipheralobjects_p.h
+ bluez/bluezperipheralconnectionmanager.cpp bluez/bluezperipheralconnectionmanager_p.h
qbluetoothdevicediscoveryagent_bluez.cpp
qbluetoothlocaldevice_bluez.cpp
qbluetoothserver_bluez.cpp
diff --git a/src/bluetooth/bluez/bluez5_helper.cpp b/src/bluetooth/bluez/bluez5_helper.cpp
index abff60aa..3b74ee64 100644
--- a/src/bluetooth/bluez/bluez5_helper.cpp
+++ b/src/bluetooth/bluez/bluez5_helper.cpp
@@ -519,6 +519,8 @@ QString findAdapterForAddress(const QBluetoothAddress &wantedAddress, bool *ok =
QString adapterWithDBusPeripheralInterface(const QBluetoothAddress &localAddress)
{
+ initializeBluez5();
+
// First find the object path to the desired adapter
bool ok = false;
const QString hostAdapterPath = findAdapterForAddress(localAddress, &ok);
diff --git a/src/bluetooth/bluez/bluezperipheralapplication.cpp b/src/bluetooth/bluez/bluezperipheralapplication.cpp
new file mode 100644
index 00000000..0da243e4
--- /dev/null
+++ b/src/bluetooth/bluez/bluezperipheralapplication.cpp
@@ -0,0 +1,276 @@
+// Copyright (C) 2022 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 "bluezperipheralapplication_p.h"
+#include "bluezperipheralobjects_p.h"
+#include "objectmanageradaptor_p.h"
+#include "gattmanager1_p.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
+
+using namespace Qt::StringLiterals;
+
+static constexpr QLatin1String appObjectPathTemplate{"/qt/btle/application/%1%2/%3"};
+
+QtBluezPeripheralApplication::QtBluezPeripheralApplication(const QString& hostAdapterPath,
+ QObject* parent)
+ : QObject(parent),
+ m_objectPath(QString(appObjectPathTemplate).
+ arg(sanitizeNameForDBus(QCoreApplication::applicationName())).
+ arg(QCoreApplication::applicationPid()).
+ arg(QRandomGenerator::global()->generate()))
+{
+ m_objectManager = new OrgFreedesktopDBusObjectManagerAdaptor(this);
+ m_gattManager = new OrgBluezGattManager1Interface("org.bluez"_L1, hostAdapterPath,
+ QDBusConnection::systemBus(), this);
+}
+
+QtBluezPeripheralApplication::~QtBluezPeripheralApplication()
+{
+ unregisterApplication();
+}
+
+void QtBluezPeripheralApplication::registerApplication()
+{
+ if (m_applicationRegistered) {
+ // Can happen eg. if advertisement is start-stop-started
+ qCDebug(QT_BT_BLUEZ) << "Bluez peripheral application already registered";
+ return;
+ }
+
+ if (m_services.isEmpty()) {
+ // Registering the application to bluez without services would fail
+ qCDebug(QT_BT_BLUEZ) << "No services, omiting Bluez peripheral application registration";
+ return;
+ }
+
+ qCDebug(QT_BT_BLUEZ) << "Registering bluez peripheral application:" << m_objectPath;
+
+ // Register this application object on DBus
+ if (!QDBusConnection::systemBus().registerObject(m_objectPath, m_objectManager,
+ QDBusConnection::ExportAllContents)) {
+ qCWarning(QT_BT_BLUEZ) << "Peripheral application object registration failed";
+ emit errorOccurred();
+ return;
+ }
+
+ // Register the service objects on DBus
+ registerServices();
+
+ // Register the gatt application to Bluez. After successful registration Bluez
+ // is aware of this peripheral application and will inquiry which services this application
+ // provides, see GetManagedObjects()
+ auto reply = m_gattManager->RegisterApplication(QDBusObjectPath(m_objectPath), {});
+ QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
+ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this,
+ [this](QDBusPendingCallWatcher* watcher) {
+ QDBusPendingReply<> reply = *watcher;
+ if (reply.isError()) {
+ qCWarning(QT_BT_BLUEZ) << "Application registration failed" << reply.error();
+ QDBusConnection::systemBus().unregisterObject(m_objectPath);
+ emit errorOccurred();
+ } else {
+ qCDebug(QT_BT_BLUEZ) << "Peripheral application registered as" << m_objectPath;
+ m_applicationRegistered = true;
+ emit registered();
+ }
+ watcher->deleteLater();
+ });
+}
+
+void QtBluezPeripheralApplication::unregisterApplication()
+{
+ if (!m_applicationRegistered)
+ return;
+ m_applicationRegistered = false;
+ auto reply = m_gattManager->UnregisterApplication(QDBusObjectPath(m_objectPath));
+ reply.waitForFinished();
+ if (reply.isError())
+ qCWarning(QT_BT_BLUEZ) << "Error in unregistering peripheral application";
+ else
+ qCDebug(QT_BT_BLUEZ) << "Peripheral application unregistered successfully";
+ QDBusConnection::systemBus().unregisterObject(m_objectPath);
+ unregisterServices();
+
+ qCDebug(QT_BT_BLUEZ) << "Unregistered Bluez peripheral application on DBus:" << m_objectPath;
+}
+
+void QtBluezPeripheralApplication::registerServices()
+{
+ // Register the service objects on DBus
+ for (const auto service: std::as_const(m_services))
+ service->registerObject();
+ for (const auto& characteristic : std::as_const(m_characteristics))
+ characteristic->registerObject();
+ for (const auto& descriptor : std::as_const(m_descriptors))
+ descriptor->registerObject();
+}
+
+void QtBluezPeripheralApplication::unregisterServices()
+{
+ // Unregister the service objects from DBus
+ for (const auto service: std::as_const(m_services))
+ service->unregisterObject();
+ for (const auto& characteristic : std::as_const(m_characteristics))
+ characteristic->unregisterObject();
+ for (const auto& descriptor : std::as_const(m_descriptors))
+ descriptor->unregisterObject();
+}
+
+void QtBluezPeripheralApplication::reset()
+{
+ unregisterApplication();
+
+ qDeleteAll(m_services);
+ m_services.clear();
+ qDeleteAll(m_descriptors);
+ m_descriptors.clear();
+ qDeleteAll(m_characteristics);
+ m_characteristics.clear();
+}
+
+void QtBluezPeripheralApplication::addService(const QLowEnergyServiceData &serviceData,
+ QSharedPointer<QLowEnergyServicePrivate> servicePrivate,
+ QLowEnergyHandle serviceHandle)
+{
+ if (m_applicationRegistered) {
+ qCWarning(QT_BT_BLUEZ) << "Adding services to a registered application is not supported "
+ "on Bluez DBus. Add services only before first advertisement or "
+ "after disconnection";
+ return;
+ }
+
+ // The ordinal numbers in the below object creation are used to create paths such as:
+ // ../service0/char0/desc0
+ // ../service0/char1/desc0
+ // ../service1/char0/desc0
+ // ../service1/char0/desc1
+ // For the Service object itself the ordinal number is the size of the service container
+ QtBluezPeripheralService* service = new QtBluezPeripheralService(
+ serviceData, m_objectPath, m_services.size(), serviceHandle, this);
+ m_services.insert(serviceHandle, service);
+
+ // Add included services
+ for (const auto includedService : serviceData.includedServices()) {
+ // As per Qt documentation the included service must have been added earlier
+ for (const auto s : std::as_const(m_services)) {
+ if (QBluetoothUuid(s->uuid) == includedService->serviceUuid()) {
+ service->addIncludedService(s->objectPath);
+ }
+ }
+ }
+
+ // Set characteristics and their descriptors
+ quint16 characteristicOrdinal{0};
+ for (const auto& characteristicData : serviceData.characteristics()) {
+ auto characteristicHandle = handleForCharacteristic(
+ characteristicData.uuid(), servicePrivate);
+ QtBluezPeripheralCharacteristic* characteristic =
+ new QtBluezPeripheralCharacteristic(characteristicData,
+ service->objectPath, characteristicOrdinal++,
+ characteristicHandle, this);
+ m_characteristics.insert(characteristicHandle, characteristic);
+ QObject::connect(characteristic, &QtBluezPeripheralCharacteristic::valueUpdatedByRemote,
+ this, &QtBluezPeripheralApplication::characteristicValueUpdatedByRemote);
+ QObject::connect(characteristic, &QtBluezPeripheralCharacteristic::remoteDeviceAccessEvent,
+ this, &QtBluezPeripheralApplication::remoteDeviceAccessEvent);
+
+ quint16 descriptorOrdinal{0};
+ for (const auto& descriptorData : characteristicData.descriptors()) {
+ // With bluez we don't use the CCCD user has provided, because Bluez
+ // generates it if 'notify/indicate' flag is set. Duplicate CCCDs would ensue
+ if (descriptorData.uuid()
+ == QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration) {
+ continue;
+ }
+ auto descriptorHandle = handleForDescriptor(descriptorData.uuid(),
+ servicePrivate,
+ characteristicHandle);
+ QtBluezPeripheralDescriptor* descriptor =
+ new QtBluezPeripheralDescriptor(descriptorData,
+ characteristic->objectPath, descriptorOrdinal++,
+ descriptorHandle, characteristicHandle, this);
+ QObject::connect(descriptor, &QtBluezPeripheralDescriptor::valueUpdatedByRemote,
+ this, &QtBluezPeripheralApplication::descriptorValueUpdatedByRemote);
+ QObject::connect(descriptor, &QtBluezPeripheralCharacteristic::remoteDeviceAccessEvent,
+ this, &QtBluezPeripheralApplication::remoteDeviceAccessEvent);
+ m_descriptors.insert(descriptorHandle, descriptor);
+ }
+ }
+}
+
+// This function is called when characteristic is written to from Qt API
+bool QtBluezPeripheralApplication::localCharacteristicWrite(QLowEnergyHandle handle,
+ const QByteArray& value)
+{
+ auto characteristic = m_characteristics.value(handle);
+ if (!characteristic) {
+ qCWarning(QT_BT_BLUEZ) << "DBus characteristic not found for write";
+ return false;
+ }
+ return characteristic->localValueUpdate(value);
+}
+
+// This function is called when characteristic is written to from Qt API
+bool QtBluezPeripheralApplication::localDescriptorWrite(QLowEnergyHandle handle,
+ const QByteArray& value)
+{
+ auto descriptor = m_descriptors.value(handle);
+ if (!descriptor) {
+ qCWarning(QT_BT_BLUEZ) << "DBus descriptor not found for write";
+ return false;
+ }
+ return descriptor->localValueUpdate(value);
+}
+
+bool QtBluezPeripheralApplication::registrationNeeded()
+{
+ return !m_applicationRegistered && !m_services.isEmpty();
+}
+
+// org.freedesktop.DBus.ObjectManager
+// This is called by Bluez when we register the application
+ManagedObjectList QtBluezPeripheralApplication::GetManagedObjects()
+{
+ ManagedObjectList managedObjects;
+ for (const auto service: std::as_const(m_services))
+ managedObjects.insert(QDBusObjectPath(service->objectPath), service->properties());
+ for (const auto& charac : std::as_const(m_characteristics))
+ managedObjects.insert(QDBusObjectPath(charac->objectPath), charac->properties());
+ for (const auto& descriptor : std::as_const(m_descriptors))
+ managedObjects.insert(QDBusObjectPath(descriptor->objectPath), descriptor->properties());
+
+ return managedObjects;
+}
+
+// Returns the Qt-internal handle for the characteristic
+QLowEnergyHandle QtBluezPeripheralApplication::handleForCharacteristic(QBluetoothUuid uuid,
+ QSharedPointer<QLowEnergyServicePrivate> service)
+{
+ const auto handles = service->characteristicList.keys();
+ for (const auto handle : handles) {
+ if (uuid == service->characteristicList[handle].uuid)
+ return handle;
+ }
+ return 0;
+}
+
+// Returns the Qt-internal handle for the descriptor
+QLowEnergyHandle QtBluezPeripheralApplication::handleForDescriptor(QBluetoothUuid uuid,
+ QSharedPointer<QLowEnergyServicePrivate> service,
+ QLowEnergyHandle characteristicHandle)
+{
+ const auto characteristicData = service->characteristicList[characteristicHandle];
+ const auto handles = characteristicData.descriptorList.keys();
+ for (const auto handle : handles) {
+ if (uuid == characteristicData.descriptorList[handle].uuid)
+ return handle;
+ }
+ return 0;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_bluezperipheralapplication_p.cpp"
diff --git a/src/bluetooth/bluez/bluezperipheralapplication_p.h b/src/bluetooth/bluez/bluezperipheralapplication_p.h
new file mode 100644
index 00000000..1b8287b9
--- /dev/null
+++ b/src/bluetooth/bluez/bluezperipheralapplication_p.h
@@ -0,0 +1,100 @@
+// Copyright (C) 2022 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
+
+#ifndef BLUEZ_PERIPHERAL_APPLICATION_P_H
+#define BLUEZ_PERIPHERAL_APPLICATION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtBluetooth/private/qlowenergycontroller_bluezdbus_p.h>
+#include "bluez5_helper_p.h"
+
+#include <QtBluetooth/QBluetoothAddress>
+#include <QtCore/QCoreApplication>
+
+class OrgFreedesktopDBusObjectManagerAdaptor;
+class OrgBluezGattManager1Interface;
+
+QT_BEGIN_NAMESPACE
+
+class QLowEnergyControllerPrivateBluezDBus;
+class QtBluezPeripheralService;
+class QtBluezPeripheralCharacteristic;
+class QtBluezPeripheralDescriptor;
+class QtBluezPeripheralConnectionManager;
+
+class QtBluezPeripheralApplication : public QObject
+{
+ Q_OBJECT
+
+public:
+ QtBluezPeripheralApplication(const QString& localAdapterPath, QObject* parent = nullptr);
+ ~QtBluezPeripheralApplication();
+
+ // Register the application and its services to DBus & Bluez
+ void registerApplication();
+ // Unregister the application and its services from DBus & Bluez.
+ // Calling this doesn't invalidate the services
+ void unregisterApplication();
+ // Unregister and release all resources, invalidates services
+ void reset();
+
+ void addService(const QLowEnergyServiceData &serviceData,
+ QSharedPointer<QLowEnergyServicePrivate> servicePrivate,
+ QLowEnergyHandle serviceHandle);
+
+ // Call these when the user application has updated the attribute value
+ // Returns whether the new value was accepted
+ bool localCharacteristicWrite(QLowEnergyHandle handle, const QByteArray& value);
+ bool localDescriptorWrite(QLowEnergyHandle handle, const QByteArray& value);
+
+ // Returns true if application has services and is not registered
+ bool registrationNeeded();
+
+ // org.freedesktop.DBus.ObjectManager
+ Q_INVOKABLE ManagedObjectList GetManagedObjects();
+
+signals:
+ void errorOccurred();
+ void registered();
+
+ // Emitted when remote device reads a characteristic
+ void remoteDeviceAccessEvent(const QString& remoteDeviceObjectPath, quint16 mtu);
+
+ // These are emitted when remote has written a new value
+ void characteristicValueUpdatedByRemote(QLowEnergyHandle handle, const QByteArray& value);
+ void descriptorValueUpdatedByRemote(QLowEnergyHandle characteristicHandle,
+ QLowEnergyHandle descriptorHandle,
+ const QByteArray& value);
+private:
+ void registerServices();
+ void unregisterServices();
+
+ QLowEnergyHandle handleForCharacteristic(QBluetoothUuid uuid,
+ QSharedPointer<QLowEnergyServicePrivate> service);
+ QLowEnergyHandle handleForDescriptor(QBluetoothUuid uuid,
+ QSharedPointer<QLowEnergyServicePrivate> service,
+ QLowEnergyHandle characteristicHandle);
+
+ QMap<QLowEnergyHandle, QtBluezPeripheralService*> m_services;
+ QMap<QLowEnergyHandle, QtBluezPeripheralCharacteristic*> m_characteristics;
+ QMap<QLowEnergyHandle, QtBluezPeripheralDescriptor*> m_descriptors;
+
+ QString m_objectPath;
+ OrgFreedesktopDBusObjectManagerAdaptor* m_objectManager{};
+ OrgBluezGattManager1Interface* m_gattManager{};
+ bool m_applicationRegistered{false};
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/bluetooth/bluez/bluezperipheralconnectionmanager.cpp b/src/bluetooth/bluez/bluezperipheralconnectionmanager.cpp
new file mode 100644
index 00000000..64350d2d
--- /dev/null
+++ b/src/bluetooth/bluez/bluezperipheralconnectionmanager.cpp
@@ -0,0 +1,96 @@
+// Copyright (C) 2022 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 "bluezperipheralconnectionmanager_p.h"
+#include "device1_bluez5_p.h"
+
+#include <QtBluetooth/QBluetoothLocalDevice>
+#include <QtDBus/QDBusConnection>
+#include <QtCore/QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
+
+using namespace Qt::StringLiterals;
+
+QtBluezPeripheralConnectionManager::QtBluezPeripheralConnectionManager(
+ const QBluetoothAddress& localAddress, QObject* parent)
+ : QObject(parent),
+ m_localDevice(new QBluetoothLocalDevice(localAddress, this))
+{
+ QObject::connect(m_localDevice, &QBluetoothLocalDevice::deviceDisconnected,
+ this, &QtBluezPeripheralConnectionManager::remoteDeviceDisconnected);
+}
+
+void QtBluezPeripheralConnectionManager::remoteDeviceAccessEvent(
+ const QString& remoteDeviceObjectPath, quint16 mtu)
+{
+ if (m_clients.contains(remoteDeviceObjectPath))
+ return; // Already aware of the client
+
+ std::unique_ptr<OrgBluezDevice1Interface> device{new OrgBluezDevice1Interface(
+ "org.bluez"_L1, remoteDeviceObjectPath,
+ QDBusConnection::systemBus(), this)};
+
+ qCDebug(QT_BT_BLUEZ) << "New LE Gatt client connected: " << remoteDeviceObjectPath
+ << device->address() << device->name() << "mtu:" << mtu;
+
+ RemoteDeviceDetails details{QBluetoothAddress{device->address()}, device->name(), mtu};
+
+ m_clients.insert(remoteDeviceObjectPath, details);
+ if (!m_connected) {
+ m_connected = true;
+ emit connectivityStateChanged(true);
+ }
+ emit remoteDeviceChanged(details.address, details.name, details.mtu);
+}
+
+void QtBluezPeripheralConnectionManager::remoteDeviceDisconnected(const QBluetoothAddress& address)
+{
+ // Find if the disconnected device was gatt client
+ bool remoteDetailsChanged{false};
+ for (auto it = m_clients.begin(); it != m_clients.end(); it++) {
+ if (it.value().address == address) {
+ qCDebug(QT_BT_BLUEZ) << "LE Gatt client disconnected:" << address;
+ remoteDetailsChanged = true;
+ m_clients.remove(it.key());
+ break;
+ }
+ }
+
+ if (!remoteDetailsChanged)
+ return;
+
+ if (m_clients.isEmpty() && m_connected) {
+ m_connected = false;
+ emit connectivityStateChanged(false);
+ }
+
+ // If a client disconnected but there are others, pick any other.
+ // Qt API doesn't distinguish between clients
+ if (!m_clients.isEmpty()) {
+ emit remoteDeviceChanged(m_clients.last().address,
+ m_clients.last().name, m_clients.last().mtu);
+ }
+}
+
+void QtBluezPeripheralConnectionManager::disconnectDevices()
+{
+ for (auto it = m_clients.begin(); it != m_clients.end(); it++) {
+ std::unique_ptr<OrgBluezDevice1Interface> device{new OrgBluezDevice1Interface(
+ "org.bluez"_L1, it.key(), QDBusConnection::systemBus())};
+ device->Disconnect();
+ }
+ reset();
+}
+
+void QtBluezPeripheralConnectionManager::reset()
+{
+ m_connected = false;
+ m_clients.clear();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_bluezperipheralconnectionmanager_p.cpp"
diff --git a/src/bluetooth/bluez/bluezperipheralconnectionmanager_p.h b/src/bluetooth/bluez/bluezperipheralconnectionmanager_p.h
new file mode 100644
index 00000000..42e839cc
--- /dev/null
+++ b/src/bluetooth/bluez/bluezperipheralconnectionmanager_p.h
@@ -0,0 +1,68 @@
+// Copyright (C) 2022 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
+
+#ifndef BLUEZ_PERIPHERAL_CONNECTION_MANAGER_P_H
+#define BLUEZ_PERIPHERAL_CONNECTION_MANAGER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtBluetooth/QBluetoothAddress>
+#include <QtBluetooth/QBluetoothUuid>
+#include <QtCore/QObject>
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothLocalDevice;
+
+/*
+ QtBluezPeripheralConnectionManager determines
+ - when remote Gatt client(s) connect and disconnect
+ - the remote device details (name, address, mtu)
+
+ 'Connected' state is assumed when first client reads a characteristic.
+ 'Disconnected' state is assumed when all such clients have disconnected.
+*/
+
+class QtBluezPeripheralConnectionManager : public QObject
+{
+ Q_OBJECT
+public:
+ QtBluezPeripheralConnectionManager(const QBluetoothAddress& localAddress,
+ QObject* parent = nullptr);
+ void reset();
+ void disconnectDevices();
+
+public slots:
+ void remoteDeviceAccessEvent(const QString& remoteDeviceObjectPath, quint16 mtu);
+
+signals:
+ void connectivityStateChanged(bool connected);
+ void remoteDeviceChanged(const QBluetoothAddress& address, const QString& name, quint16 mtu);
+
+private slots:
+ void remoteDeviceDisconnected(const QBluetoothAddress& address);
+
+private:
+ struct RemoteDeviceDetails {
+ QBluetoothAddress address;
+ QString name;
+ quint16 mtu;
+ };
+ bool m_connected{false};
+ QString m_hostAdapterPath;
+ QMap<QString, RemoteDeviceDetails> m_clients;
+ QBluetoothLocalDevice* m_localDevice;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/bluetooth/bluez/bluezperipheralobjects.cpp b/src/bluetooth/bluez/bluezperipheralobjects.cpp
new file mode 100644
index 00000000..090005ab
--- /dev/null
+++ b/src/bluetooth/bluez/bluezperipheralobjects.cpp
@@ -0,0 +1,336 @@
+// Copyright (C) 2022 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 "bluezperipheralobjects_p.h"
+
+#include "propertiesadaptor_p.h"
+#include "gattservice1adaptor_p.h"
+#include "gattcharacteristic1adaptor_p.h"
+#include "gattdescriptor1adaptor_p.h"
+
+#include <QtCore/QLoggingCategory>
+#include <QtDBus/QDBusConnection>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
+
+using namespace Qt::StringLiterals;
+
+static constexpr QLatin1String characteristicPathTemplate{"%1/char%2"};
+static constexpr QLatin1String descriptorPathTemplate{"%1/desc%2"};
+static constexpr QLatin1String servicePathTemplate{"%1/service%2"};
+
+static constexpr QLatin1String bluezServiceInterface("org.bluez.GattService1");
+static constexpr QLatin1String bluezCharacteristicInterface("org.bluez.GattCharacteristic1");
+static constexpr QLatin1String bluezDescriptorInterface("org.bluez.GattDescriptor1");
+
+QtBluezPeripheralGattObject::QtBluezPeripheralGattObject(const QString& objectPath,
+ const QString& uuid, QLowEnergyHandle handle, QObject* parent)
+ : QObject(parent), objectPath(objectPath), uuid(uuid), handle(handle),
+ propertiesAdaptor(new OrgFreedesktopDBusPropertiesAdaptor(this))
+{}
+
+QtBluezPeripheralGattObject::~QtBluezPeripheralGattObject()
+{
+ unregisterObject();
+}
+
+bool QtBluezPeripheralGattObject::registerObject()
+{
+ if (m_registered)
+ return true;
+
+ if (QDBusConnection::systemBus().registerObject(objectPath, this)) {
+ qCDebug(QT_BT_BLUEZ) << "Registered object on DBus:" << objectPath << uuid;
+ m_registered = true;
+ return true;
+ } else {
+ qCWarning(QT_BT_BLUEZ) << "Failed to register object on DBus:" << objectPath << uuid;
+ return false;
+ }
+}
+
+void QtBluezPeripheralGattObject::unregisterObject()
+{
+ if (!m_registered)
+ return;
+ QDBusConnection::systemBus().unregisterObject(objectPath);
+ qCDebug(QT_BT_BLUEZ) << "Unregistered object on DBus:" << objectPath << uuid;
+ m_registered = false;
+}
+
+void QtBluezPeripheralGattObject::accessEvent(const QVariantMap& options)
+{
+ // Report this event for connection management purposes
+ const auto remoteDevice = options.value("device"_L1).value<QDBusObjectPath>().path();
+ if (!remoteDevice.isEmpty())
+ emit remoteDeviceAccessEvent(remoteDevice, options.value("mtu"_L1).toUInt());
+}
+
+QtBluezPeripheralDescriptor::QtBluezPeripheralDescriptor(
+ const QLowEnergyDescriptorData& descriptorData,
+ const QString& characteristicPath, quint16 ordinal,
+ QLowEnergyHandle handle, QLowEnergyHandle characteristicHandle,
+ QObject* parent)
+ : QtBluezPeripheralGattObject(descriptorPathTemplate.arg(characteristicPath).arg(ordinal),
+ descriptorData.uuid().toString(QUuid::WithoutBraces), handle, parent),
+ m_adaptor(new OrgBluezGattDescriptor1Adaptor(this)),
+ m_characteristicPath(characteristicPath),
+ m_value(descriptorData.value()),
+ m_characteristicHandle(characteristicHandle)
+{
+ initializeFlags(descriptorData);
+}
+
+InterfaceList QtBluezPeripheralDescriptor::properties() const
+{
+ InterfaceList properties;
+ properties.insert(bluezDescriptorInterface,
+ {
+ {"UUID"_L1, uuid},
+ {"Characteristic"_L1, QDBusObjectPath(m_characteristicPath)},
+ {"Flags"_L1, m_flags}
+ });
+ return properties;
+}
+
+// org.bluez.GattDescriptor1
+// This function is invoked when remote device reads the value
+QByteArray QtBluezPeripheralDescriptor::ReadValue(const QVariantMap &options)
+{
+ accessEvent(options);
+ // Offset is set by Bluez when the value size is more than MTU size.
+ // Bluez deduces the value size from the first ReadValue. If the
+ // received data size is larger than MTU, Bluez will take the first MTU bytes and
+ // issue more ReadValue calls with the 'offset' set
+ const quint16 offset = options.value("offset"_L1).toUInt();
+ const quint16 mtu = options.value("mtu"_L1).toUInt();
+
+ if (offset > 0)
+ return m_value.mid(offset, mtu);
+ else
+ return m_value;
+}
+
+// org.bluez.GattDescriptor1
+// This function is invoked when remote device writes a value
+void QtBluezPeripheralDescriptor::WriteValue(const QByteArray &value, const QVariantMap &options)
+{
+ accessEvent(options);
+
+ m_value = value;
+ emit valueUpdatedByRemote(m_characteristicHandle, handle, value);
+}
+
+// This function is called when the value has been updated locally (server-side)
+bool QtBluezPeripheralDescriptor::localValueUpdate(const QByteArray& value)
+{
+ m_value = value;
+ return true;
+}
+
+void QtBluezPeripheralDescriptor::initializeFlags(const QLowEnergyDescriptorData& data)
+{
+ // Flag tokens are from org.bluez.GattDescriptor1 documentation
+ if (data.isReadable())
+ m_flags.append("read"_L1);
+ if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
+ m_flags.append("encrypt-read"_L1);
+ if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
+ m_flags.append("encrypt-authenticated-read"_L1);
+
+ if (data.isWritable())
+ m_flags.append("write"_L1);
+ if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
+ m_flags.append("encrypt-write"_L1);
+ if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
+ m_flags.append("encrypt-authenticated-write"_L1);
+
+ if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired
+ || data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired)
+ m_flags.append("authorize"_L1);
+
+ if (m_flags.isEmpty()) {
+ qCWarning(QT_BT_BLUEZ) << "Descriptor property flags not set" << uuid
+ << "Peripheral may fail to register";
+ }
+}
+
+QtBluezPeripheralCharacteristic::QtBluezPeripheralCharacteristic(
+ const QLowEnergyCharacteristicData& characteristicData,
+ const QString& servicePath, quint16 ordinal,
+ QLowEnergyHandle handle, QObject* parent)
+ : QtBluezPeripheralGattObject(characteristicPathTemplate.arg(servicePath).arg(ordinal),
+ characteristicData.uuid().toString(QUuid::WithoutBraces), handle, parent),
+ m_adaptor(new OrgBluezGattCharacteristic1Adaptor(this)),
+ m_servicePath(servicePath),
+ m_minimumValueLength(characteristicData.minimumValueLength()),
+ m_maximumValueLength(characteristicData.maximumValueLength())
+{
+ initializeFlags(characteristicData);
+ initializeValue(characteristicData.value());
+}
+
+InterfaceList QtBluezPeripheralCharacteristic::properties() const
+{
+ InterfaceList properties;
+ properties.insert(bluezCharacteristicInterface,
+ {
+ {"UUID"_L1, uuid},
+ {"Service"_L1, QDBusObjectPath(m_servicePath)},
+ {"Flags"_L1, m_flags}
+ });
+ return properties;
+}
+
+// org.bluez.GattCharacteristic1
+// This function is invoked when remote device reads the value
+QByteArray QtBluezPeripheralCharacteristic::ReadValue(const QVariantMap &options)
+{
+ accessEvent(options);
+ // Offset is set by Bluez when the value size is more than MTU size.
+ // Bluez deduces the value size from the first ReadValue. If the
+ // received data size is larger than MTU, Bluez will take the first MTU bytes and
+ // issue more ReadValue calls with the 'offset' set
+ const quint16 offset = options.value("offset"_L1).toUInt();
+ const quint16 mtu = options.value("mtu"_L1).toUInt();
+
+ if (offset > 0)
+ return m_value.mid(offset, mtu);
+ else
+ return m_value;
+}
+
+// org.bluez.GattCharacteristic1
+// This function is invoked when remote device writes a value
+void QtBluezPeripheralCharacteristic::WriteValue(const QByteArray &value, const QVariantMap &options)
+{
+ accessEvent(options);
+
+ if (value.size() < m_minimumValueLength || value.size() > m_maximumValueLength) {
+ qCWarning(QT_BT_BLUEZ) << "Characteristic value has invalid length" << value.size()
+ << "min:" << m_minimumValueLength
+ << "max:" << m_maximumValueLength;
+ return;
+ }
+ m_value = value;
+ emit valueUpdatedByRemote(handle, value);
+}
+
+// This function is called when the value has been updated locally (server-side)
+bool QtBluezPeripheralCharacteristic::localValueUpdate(const QByteArray& value)
+{
+ if (value.size() < m_minimumValueLength || value.size() > m_maximumValueLength) {
+ qCWarning(QT_BT_BLUEZ) << "Characteristic value has invalid length" << value.size()
+ << "min:" << m_minimumValueLength
+ << "max:" << m_maximumValueLength;
+ return false;
+ }
+ m_value = value;
+ if (m_notifying) {
+ emit propertiesAdaptor->PropertiesChanged(
+ bluezCharacteristicInterface, {{"Value"_L1, m_value}}, {});
+ }
+ return true;
+}
+
+// org.bluez.GattCharacteristic1
+// These are called when remote client enables or disables NTF/IND
+void QtBluezPeripheralCharacteristic::StartNotify()
+{
+ qCDebug(QT_BT_BLUEZ) << "NTF or IND enabled for characteristic" << uuid;
+ m_notifying = true;
+}
+
+void QtBluezPeripheralCharacteristic::StopNotify()
+{
+ qCDebug(QT_BT_BLUEZ) << "NTF or IND disabled for characteristic" << uuid;
+ m_notifying = false;
+}
+
+
+void QtBluezPeripheralCharacteristic::initializeValue(const QByteArray& value)
+{
+ const auto valueSize = value.size();
+ if (valueSize < m_minimumValueLength || valueSize > m_maximumValueLength) {
+ qCWarning(QT_BT_BLUEZ) << "Characteristic value has invalid length" << valueSize
+ << "min:" << m_minimumValueLength
+ << "max:" << m_maximumValueLength;
+ m_value = QByteArray(m_minimumValueLength, 0);
+ } else {
+ m_value = value;
+ }
+}
+
+void QtBluezPeripheralCharacteristic::initializeFlags(const QLowEnergyCharacteristicData& data)
+{
+ // Flag tokens are from org.bluez.GattCharacteristic1 documentation
+ if (data.properties() & QLowEnergyCharacteristic::PropertyType::Broadcasting)
+ m_flags.append("broadcast"_L1);
+ if (data.properties() & QLowEnergyCharacteristic::PropertyType::WriteNoResponse)
+ m_flags.append("write-without-response"_L1);
+ if (data.properties() & QLowEnergyCharacteristic::PropertyType::Read)
+ m_flags.append("read"_L1);
+ if (data.properties() & QLowEnergyCharacteristic::PropertyType::Write)
+ m_flags.append("write"_L1);
+ if (data.properties() & QLowEnergyCharacteristic::PropertyType::Notify)
+ m_flags.append("notify"_L1);
+ if (data.properties() & QLowEnergyCharacteristic::PropertyType::Indicate)
+ m_flags.append("indicate"_L1);
+ if (data.properties() & QLowEnergyCharacteristic::PropertyType::WriteSigned)
+ m_flags.append("authenticated-signed-writes"_L1);
+ // TODO how to interpet ExtendedProperty? (reliable-write and writable-auxiliaries?)
+ // (Note: Bluez will generate any needed special descriptors). Also: check
+ // the secure-read and secure-write flags
+ if (data.properties() & QLowEnergyCharacteristic::PropertyType::ExtendedProperty)
+ m_flags.append("extended-properties"_L1);
+
+ if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
+ m_flags.append("encrypt-read"_L1);
+ if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
+ m_flags.append("encrypt-authenticated-read"_L1);
+ if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
+ m_flags.append("encrypt-write"_L1);
+ if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired)
+ m_flags.append("encrypt-authenticated-write"_L1);
+
+ if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired
+ || data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired)
+ m_flags.append("authorize"_L1);
+
+ if (m_flags.isEmpty()) {
+ qCWarning(QT_BT_BLUEZ) << "Characteristic property flags not set" << uuid
+ << "Peripheral may fail to register";
+ }
+}
+
+
+QtBluezPeripheralService::QtBluezPeripheralService(const QLowEnergyServiceData &serviceData,
+ const QString& applicationPath, quint16 ordinal,
+ QLowEnergyHandle handle, QObject* parent)
+ : QtBluezPeripheralGattObject(servicePathTemplate.arg(applicationPath).arg(ordinal),
+ serviceData.uuid().toString(QUuid::WithoutBraces), handle, parent),
+ m_isPrimary(serviceData.type() == QLowEnergyServiceData::ServiceTypePrimary),
+ m_adaptor(new OrgBluezGattService1Adaptor(this))
+{
+}
+
+void QtBluezPeripheralService::addIncludedService(const QString& objectPath) {
+ qCDebug(QT_BT_BLUEZ) << "Adding included service" << objectPath << "for" << uuid;
+ m_includedServices.append(QDBusObjectPath(objectPath));
+}
+
+InterfaceList QtBluezPeripheralService::properties() const {
+ InterfaceList interfaces;
+ interfaces.insert(bluezServiceInterface,{
+ {"UUID"_L1, uuid},
+ {"Primary"_L1, m_isPrimary},
+ {"Includes"_L1, QVariant::fromValue(m_includedServices)}
+ });
+ return interfaces;
+};
+
+QT_END_NAMESPACE
+
+#include "moc_bluezperipheralobjects_p.cpp"
diff --git a/src/bluetooth/bluez/bluezperipheralobjects_p.h b/src/bluetooth/bluez/bluezperipheralobjects_p.h
new file mode 100644
index 00000000..69187b9c
--- /dev/null
+++ b/src/bluetooth/bluez/bluezperipheralobjects_p.h
@@ -0,0 +1,171 @@
+// Copyright (C) 2022 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
+
+#ifndef BLUEZ_PERIPHERAL_OBJECTS_P_H
+#define BLUEZ_PERIPHERAL_OBJECTS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "bluez5_helper_p.h"
+
+#include <QtBluetooth/qbluetooth.h>
+#include <QtBluetooth/QBluetoothUuid>
+#include <QtBluetooth/QLowEnergyDescriptorData>
+#include <QtBluetooth/QLowEnergyCharacteristicData>
+#include <QtBluetooth/QLowEnergyServiceData>
+
+class OrgFreedesktopDBusPropertiesAdaptor;
+class OrgBluezGattCharacteristic1Adaptor;
+class OrgBluezGattDescriptor1Adaptor;
+class OrgBluezGattService1Adaptor;
+
+QT_BEGIN_NAMESPACE
+
+// The QtBluezPeripheralGattObject is the base class for services, characteristics, and descriptors
+class QtBluezPeripheralGattObject : public QObject
+{
+ Q_OBJECT
+
+public:
+ QtBluezPeripheralGattObject(const QString& objectPath, const QString& uuid,
+ QLowEnergyHandle handle, QObject* parent = nullptr);
+ virtual ~QtBluezPeripheralGattObject();
+
+ // List of properties exposed by this object, used when Bluez inquiries details
+ virtual InterfaceList properties() const = 0;
+
+ bool registerObject();
+ void unregisterObject();
+
+public:
+ // DBus object path
+ QString objectPath;
+ // UUID or the gatt object
+ QString uuid;
+ // QtBluetooth internal handle and reference to the application
+ // to read and write values towards the Qt API
+ QLowEnergyHandle handle;
+ // Bluez DBus Gatt objects need to provide this
+ OrgFreedesktopDBusPropertiesAdaptor* propertiesAdaptor{};
+
+signals:
+ void remoteDeviceAccessEvent(const QString& remoteDeviceObjectPath, quint16 mtu);
+
+protected:
+ void accessEvent(const QVariantMap& options);
+
+private:
+ bool m_registered = false;
+};
+
+class QtBluezPeripheralDescriptor : public QtBluezPeripheralGattObject
+{
+ Q_OBJECT
+
+public:
+ QtBluezPeripheralDescriptor(const QLowEnergyDescriptorData& descriptorData,
+ const QString& characteristicPath, quint16 ordinal,
+ QLowEnergyHandle handle, QLowEnergyHandle characteristicHandle,
+ QObject* parent);
+
+ InterfaceList properties() const final;
+
+ // org.bluez.GattDescriptor1
+ // This function is invoked when remote device reads the value
+ Q_INVOKABLE QByteArray ReadValue(const QVariantMap &options);
+
+ // org.bluez.GattDescriptor1
+ // This function is invoked when remote device writes a value
+ Q_INVOKABLE void WriteValue(const QByteArray &value, const QVariantMap &options);
+
+ // Call this function when value has been updated locally (server/user application side)
+ bool localValueUpdate(const QByteArray& value);
+
+signals:
+ void valueUpdatedByRemote(QLowEnergyHandle characteristicHandle,
+ QLowEnergyHandle descriptorHandle, const QByteArray& value);
+
+private:
+ void initializeFlags(const QLowEnergyDescriptorData& data);
+
+ OrgBluezGattDescriptor1Adaptor* m_adaptor{};
+ QString m_characteristicPath;
+ QByteArray m_value;
+ QStringList m_flags;
+ QLowEnergyHandle m_characteristicHandle;
+};
+
+
+class QtBluezPeripheralCharacteristic : public QtBluezPeripheralGattObject
+{
+ Q_OBJECT
+
+public:
+ QtBluezPeripheralCharacteristic(const QLowEnergyCharacteristicData& characteristicData,
+ const QString& servicePath, quint16 ordinal,
+ QLowEnergyHandle handle, QObject* parent);
+
+ InterfaceList properties() const final;
+
+ // org.bluez.GattCharacteristic1
+ // This function is invoked when remote device reads the value
+ Q_INVOKABLE QByteArray ReadValue(const QVariantMap &options);
+
+ // org.bluez.GattCharacteristic1
+ // This function is invoked when remote device writes a value
+ Q_INVOKABLE void WriteValue(const QByteArray &value, const QVariantMap &options);
+
+ // org.bluez.GattCharacteristic1
+ // These are called when remote client enables or disables NTF/IND
+ Q_INVOKABLE void StartNotify();
+ Q_INVOKABLE void StopNotify();
+
+ // Call this function when value has been updated locally (server/user application side)
+ bool localValueUpdate(const QByteArray& value);
+
+signals:
+ void valueUpdatedByRemote(QLowEnergyHandle handle, const QByteArray& value);
+
+private:
+ void initializeValue(const QByteArray& value);
+ void initializeFlags(const QLowEnergyCharacteristicData& data);
+
+ OrgBluezGattCharacteristic1Adaptor* m_adaptor{};
+ QString m_servicePath;
+ bool m_notifying{false};
+ QByteArray m_value;
+ QStringList m_flags;
+ int m_minimumValueLength;
+ int m_maximumValueLength;
+};
+
+class QtBluezPeripheralService : public QtBluezPeripheralGattObject
+{
+ Q_OBJECT
+public:
+ QtBluezPeripheralService(const QLowEnergyServiceData &serviceData,
+ const QString& applicationPath, quint16 ordinal,
+ QLowEnergyHandle handle, QObject* parent);
+
+ InterfaceList properties() const final;
+ void addIncludedService(const QString& objectPath);
+
+private:
+ const bool m_isPrimary;
+ OrgBluezGattService1Adaptor* m_adaptor{};
+ QList<QDBusObjectPath> m_includedServices;
+};
+
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/bluetooth/bluez/leadvertisement1.cpp b/src/bluetooth/bluez/leadvertisement1.cpp
index 20c78edd..e1b91c4a 100644
--- a/src/bluetooth/bluez/leadvertisement1.cpp
+++ b/src/bluetooth/bluez/leadvertisement1.cpp
@@ -134,4 +134,3 @@ void OrgBluezLEAdvertisement1Adaptor::Release()
// handle method call org.bluez.LEAdvertisement1.Release
QMetaObject::invokeMethod(parent(), "Release");
}
-
diff --git a/src/bluetooth/doc/src/bluetooth-index.qdoc b/src/bluetooth/doc/src/bluetooth-index.qdoc
index 8f8bab4b..7f2a0315 100644
--- a/src/bluetooth/doc/src/bluetooth-index.qdoc
+++ b/src/bluetooth/doc/src/bluetooth-index.qdoc
@@ -80,6 +80,23 @@ or Qt was built without Qt D-Bus support.
The usage of the dummy backend is highlighted via an appropriate warning while building and running.
+\section3 Linux Specific
+
+Since Qt 6.5 the Linux peripheral support has two backend alternatives:
+BlueZ DBus and Bluetooth kernel API. The Bluez DBus backend introduced in Qt 6.5 is
+intended as the eventual successor of the lower abstraction level kernel backend.
+
+The BlueZ DBus API is more limited in terms of features and is therefore not
+the default backend for compatibility reasons.
+
+The BlueZ DBus backend can be configured to use by setting
+the \e QT_BLUETOOTH_USE_DBUS_PERIPHERAL environment variable. The QLowEnergyController
+peripheral instantiation then introspects the presence of BlueZ DBus peripheral APIs
+on the local bluetooth adapter. The minimum version requirement for BlueZ is 5.56.
+
+One noteworthy difference is that with BlueZ DBus peripheral backend the user process no longer
+needs to have the \e CAP_NET_ADMIN capability (eg. run as root).
+
\section3 \macos Specific
The Bluetooth API on \macos requires a certain type of event dispatcher
that in Qt causes a dependency to \l QGuiApplication. However, you can set the
diff --git a/src/bluetooth/qleadvertiser_bluezdbus.cpp b/src/bluetooth/qleadvertiser_bluezdbus.cpp
index d94be4ee..78e95a76 100644
--- a/src/bluetooth/qleadvertiser_bluezdbus.cpp
+++ b/src/bluetooth/qleadvertiser_bluezdbus.cpp
@@ -22,7 +22,6 @@ static constexpr auto bluezErrorFailed{"org.bluez.Error.Failed"_L1};
// From bluez API documentation
static constexpr auto advDataTXPower{"tx-power"_L1};
-static constexpr auto advDataLocalName{"local-name"_L1};
static constexpr auto advDataTypePeripheral{"peripheral"_L1};
static constexpr auto advDataTypeBroadcast{"broadcast"_L1};
static constexpr quint16 advDataMinIntervalMs{20};
@@ -102,20 +101,22 @@ void QLeDBusAdvertiser::setAdvertisingParamsForDBus()
}
// Advertisement interval (min max in milliseconds). Ensure the values fit the range bluez
- // allows. The max >= min is guaranteed by QLowEnergyAdvertisingParameters::setInterval()
- m_advDataDBus->setMinInterval(qBound(
- advDataMinIntervalMs, quint16(m_advParams.minimumInterval()),
- advDataMaxIntervalMs));
- m_advDataDBus->setMaxInterval(qBound(
- advDataMinIntervalMs, quint16(m_advParams.maximumInterval()),
- advDataMaxIntervalMs));
+ // allows. The max >= min is guaranteed by QLowEnergyAdvertisingParameters::setInterval().
+ // Note: Bluez reads these values but at the time of this writing it marks this feature
+ // as 'experimental'
+ m_advDataDBus->setMinInterval(qBound(advDataMinIntervalMs,
+ quint16(m_advParams.minimumInterval()),
+ advDataMaxIntervalMs));
+ m_advDataDBus->setMaxInterval(qBound(advDataMinIntervalMs,
+ quint16(m_advParams.maximumInterval()),
+ advDataMaxIntervalMs));
}
void QLeDBusAdvertiser::setAdvertisementDataForDBus()
{
// We don't calculate the advertisement length to guard for too long advertisements.
// There isn't adequate control and visibility on the advertisement for that.
- // - We don't know the max length (legacy or extended avertising)
+ // - We don't know the max length (legacy or extended advertising)
// - Bluez may truncate some of the fields on its own, making calculus here imprecise
// - Scan response may or may not be used to offload some of the data
@@ -145,7 +146,7 @@ void QLeDBusAdvertiser::setAdvertisementDataForDBus()
{m_advData.manufacturerId(), QDBusVariant(m_advData.manufacturerData())}});
}
- // Discoverability. Bluez dbus doesn't seem to provide a match for Qt API semantics
+ // Discoverability
if (m_advDataDBus->type() == advDataTypePeripheral) {
m_advDataDBus->setDiscoverable(m_advData.discoverability()
!= QLowEnergyAdvertisingData::DiscoverabilityNone);
@@ -160,7 +161,7 @@ void QLeDBusAdvertiser::setAdvertisementDataForDBus()
void QLeDBusAdvertiser::startAdvertising()
{
- qCDebug(QT_BT_BLUEZ) << "Start advertising" << m_advObjectPath << m_advManager->path();
+ qCDebug(QT_BT_BLUEZ) << "Start advertising" << m_advObjectPath << "on" << m_advManager->path();
if (m_advertising) {
qCWarning(QT_BT_BLUEZ) << "Start tried while already advertising";
return;
@@ -209,7 +210,6 @@ void QLeDBusAdvertiser::stopAdvertising()
else
qCDebug(QT_BT_BLUEZ) << "Advertisement unregistered successfully";
QDBusConnection::systemBus().unregisterObject(m_advObjectPath);
- emit advertisingStopped();
}
// Called by Bluez when the advertisement has been removed (org.bluez.LEAdvertisement1.Release)
diff --git a/src/bluetooth/qleadvertiser_bluezdbus_p.h b/src/bluetooth/qleadvertiser_bluezdbus_p.h
index a153138e..5a119884 100644
--- a/src/bluetooth/qleadvertiser_bluezdbus_p.h
+++ b/src/bluetooth/qleadvertiser_bluezdbus_p.h
@@ -22,11 +22,11 @@ QT_REQUIRE_CONFIG(bluez);
#include <QtCore/QObject>
-QT_BEGIN_NAMESPACE
-
class OrgBluezLEAdvertisement1Adaptor;
class OrgBluezLEAdvertisingManager1Interface;
+QT_BEGIN_NAMESPACE
+
class QLeDBusAdvertiser : public QObject
{
Q_OBJECT
@@ -46,7 +46,6 @@ public:
signals:
void errorOccurred();
- void advertisingStopped();
private:
void setDataForDBus();
diff --git a/src/bluetooth/qlowenergyadvertisingdata.cpp b/src/bluetooth/qlowenergyadvertisingdata.cpp
index 8bc57b5d..90cf4529 100644
--- a/src/bluetooth/qlowenergyadvertisingdata.cpp
+++ b/src/bluetooth/qlowenergyadvertisingdata.cpp
@@ -42,6 +42,9 @@ public:
bytes. If the variable-length data set via this class exceeds that limit, it will
be left out of the packet or truncated, depending on the type.
On Android, advertising will fail if advertising data is larger than 31 bytes.
+ On Bluez DBus backend the advertising length limit and the behavior when it is exceeded
+ is up to BlueZ; it may for example support extended advertising. For the most
+ predictable behavior keep the advertising data short.
\sa QLowEnergyAdvertisingParameters
\sa QLowEnergyController::startAdvertising()
@@ -208,6 +211,9 @@ QList<QBluetoothUuid> QLowEnergyAdvertisingData::services() const
This can be used to send non-standard data.
\note If \a data is longer than 31 bytes, it will be truncated. It is the caller's responsibility
to ensure that \a data is well-formed.
+
+ Providing the raw advertising data is not supported on BlueZ DBus backend as BlueZ does not
+ support it. This may change in a future release.
*/
void QLowEnergyAdvertisingData::setRawData(const QByteArray &data)
{
diff --git a/src/bluetooth/qlowenergyadvertisingparameters.cpp b/src/bluetooth/qlowenergyadvertisingparameters.cpp
index ca8f7e1e..b0923036 100644
--- a/src/bluetooth/qlowenergyadvertisingparameters.cpp
+++ b/src/bluetooth/qlowenergyadvertisingparameters.cpp
@@ -170,6 +170,8 @@ QLowEnergyAdvertisingParameters::Mode QLowEnergyAdvertisingParameters::mode() co
Sets the white list that is potentially used for filtering scan and connection requests.
The \a whiteList parameter is the list of addresses to use for filtering, and \a policy
specifies how exactly to use \a whiteList.
+
+ Whitelists are not supported on the BlueZ DBus backend as they are not supported by BlueZ.
*/
void QLowEnergyAdvertisingParameters::setWhiteList(const QList<AddressInfo> &whiteList,
FilterPolicy policy)
@@ -204,6 +206,10 @@ QLowEnergyAdvertisingParameters::FilterPolicy QLowEnergyAdvertisingParameters::f
\note There are limits for the minimum and maximum interval; the exact values depend on
the mode. If they are exceeded, the lowest or highest possible value will be used,
respectively.
+
+ Setting the advertising interval is supported on BlueZ DBus backend if its experimental
+ status is changed in later versions of BlueZ (or run in experimental mode).
+
*/
void QLowEnergyAdvertisingParameters::setInterval(quint16 minimum, quint16 maximum)
{
diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp
index 68737403..1cdf03bb 100644
--- a/src/bluetooth/qlowenergycontroller.cpp
+++ b/src/bluetooth/qlowenergycontroller.cpp
@@ -197,7 +197,8 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
\l PeripheralRole. On iOS and macOS the controller only guesses that some central
connected to our peripheral as soon as this central tries to write/read a
characteristic/descriptor. On Android the controller monitors all connected GATT
- devices.
+ devices. On Linux BlueZ DBus peripheral backend the remote is considered connected
+ when it first reads/writes a characteristic or a descriptor.
*/
/*!
@@ -230,7 +231,9 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
This signal is emitted when the controller disconnects from the remote
Low Energy device or vice versa. On iOS and macOS this signal is unreliable
if the controller is in the \l PeripheralRole. On Android the signal is emitted
- when the last connected device is disconnected.
+ when the last connected device is disconnected. On BlueZ DBus backend the controller
+ is considered disconnected when last client which has accessed the attributes has
+ disconnected.
*/
/*!
@@ -312,20 +315,27 @@ static QLowEnergyControllerPrivate *privateController(
// with an environment variable (see bluetoothdVersion())
//
// Peripheral role
- // For the dbus peripheral backend we check the presence of the required DBus APIs, and in
- // addition the application needs to opt-in to the DBus peripheral role by setting the
- // environment variable. Otherwise we fall back to the kernel ATT backend
+ // For the dbus peripheral backend we check the presence of the required DBus APIs,
+ // bluez version, and in addition the application needs to opt-in to the DBus peripheral
+ // role by setting the environment variable. Otherwise we fall back to the kernel ATT
+ // backend
//
// ### Qt 7 consider removing the non-dbus bluez (kernel ATT) support
+
+ QString adapterPathWithPeripheralSupport;
+ if (role == QLowEnergyController::PeripheralRole
+ && bluetoothdVersion() >= QVersionNumber(5, 56)
+ && qEnvironmentVariableIsSet("QT_BLUETOOTH_USE_DBUS_PERIPHERAL")) {
+ adapterPathWithPeripheralSupport = adapterWithDBusPeripheralInterface(localDevice);
+ }
+
if (role == QLowEnergyController::CentralRole
&& bluetoothdVersion() >= QVersionNumber(5, 42)) {
qCDebug(QT_BT) << "Using BlueZ LE DBus API for central";
return new QLowEnergyControllerPrivateBluezDBus();
- } else if (role == QLowEnergyController::PeripheralRole
- && qEnvironmentVariableIsSet("BLUETOOTH_USE_DBUS_PERIPHERAL")
- && !adapterWithDBusPeripheralInterface(localDevice).isEmpty()) {
+ } else if (!adapterPathWithPeripheralSupport.isEmpty()) {
qCDebug(QT_BT) << "Using BlueZ LE DBus API for peripheral";
- return new QLowEnergyControllerPrivateBluezDBus();
+ return new QLowEnergyControllerPrivateBluezDBus(adapterPathWithPeripheralSupport);
} else {
qCDebug(QT_BT) << "Using BlueZ kernel ATT interface for"
<< (role == QLowEnergyController::CentralRole ? "central" : "peripheral");
@@ -754,6 +764,10 @@ QLowEnergyService *QLowEnergyController::createServiceObject(
the advertised packets may not contain all uuids. The existing limit may have caused the truncation
of uuids. In such cases \a scanResponseData may be used for additional information.
+ On BlueZ DBus backend BlueZ decides if, and which data, to use in a scan response. Therefore
+ all advertisement data is recommended to set in the main \a advertisingData parameter. If both
+ advertisement and scan response data is set, the scan response data is given precedence.
+
If this object is currently not in the \l UnconnectedState, nothing happens.
\since 5.7
@@ -857,7 +871,7 @@ QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData
an acknowledged Android bug. Due to this bug Android does not emit the \l connectionUpdated()
signal.
- \note Currently, this functionality is only implemented on Linux and Android.
+ \note Currently, this functionality is only implemented on Linux kernel backend and Android.
\sa connectionUpdated()
\since 5.7
diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp
index 94f0757e..8d78b376 100644
--- a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp
+++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp
@@ -11,14 +11,17 @@
#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)
{
}
@@ -32,6 +35,42 @@ QLowEnergyControllerPrivateBluezDBus::~QLowEnergyControllerPrivateBluezDBus()
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(
@@ -220,6 +259,16 @@ void QLowEnergyControllerPrivateBluezDBus::resetController()
advertiser = nullptr;
}
+ if (peripheralApplication)
+ peripheralApplication->reset();
+
+ if (peripheralConnectionManager)
+ peripheralConnectionManager->reset();
+
+ remoteName.clear();
+ remoteDevice.clear();
+ remoteMtu = -1;
+
dbusServices.clear();
jobs.clear();
invalidateServices();
@@ -347,26 +396,36 @@ 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::UnknownError);
- } else {
- 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();
+ const auto emitDisconnected = (state == QLowEnergyController::ConnectedState);
+ setState(QLowEnergyController::UnconnectedState);
+ if (emitDisconnected)
+ emit q->disconnected();
+ }
}
void QLowEnergyControllerPrivateBluezDBus::discoverServices()
@@ -1233,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;
}
}
@@ -1282,8 +1349,20 @@ 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;
}
}
@@ -1295,68 +1374,140 @@ void QLowEnergyControllerPrivateBluezDBus::startAdvertising(
error = QLowEnergyController::NoError;
errorString.clear();
+ Q_ASSERT(peripheralApplication);
+ Q_ASSERT(!adapterPathWithPeripheralSupport.isEmpty());
+
if (advertiser) {
- // Clear any previous advertiser to start anew
- advertiser->stopAdvertising();
+ // Clear any previous advertiser in case advertising data has changed.
+ // For clarity: this function is called only in 'Unconnected' state
delete advertiser;
advertiser = nullptr;
}
-
- const QString hostAdapterPath = adapterWithDBusPeripheralInterface(localAdapter);
- if (hostAdapterPath.isEmpty()) {
- qCWarning(QT_BT_BLUEZ) << "Cannot find suitable bluetooth adapter";
- setError(QLowEnergyController::InvalidBluetoothAdapterError);
- return;
- }
-
advertiser = new QLeDBusAdvertiser(params, advertisingData, scanResponseData,
- hostAdapterPath, this);
+ adapterPathWithPeripheralSupport, this);
connect(advertiser, &QLeDBusAdvertiser::errorOccurred,
this, &QLowEnergyControllerPrivateBluezDBus::handleAdvertisingError);
- connect(advertiser, &QLeDBusAdvertiser::advertisingStopped,
- this, &QLowEnergyControllerPrivateBluezDBus::handleAdvertisingStopped);
setState(QLowEnergyController::AdvertisingState);
- advertiser->startAdvertising();
+
+ // 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()
{
- if (advertiser)
+ // 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::handleAdvertisingStopped()
+void QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationError()
{
- qCDebug(QT_BT_BLUEZ) << "Advertising stopped";
- if (state == QLowEnergyController::AdvertisingState)
+ 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();
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)
{
- // TODO create services
+ Q_ASSERT(peripheralApplication);
+ QSharedPointer<QLowEnergyServicePrivate> servicePrivate = serviceForHandle(startHandle);
+ if (servicePrivate.isNull())
+ return;
+ peripheralApplication->addService(serviceData, servicePrivate, startHandle);
}
int QLowEnergyControllerPrivateBluezDBus::mtu() const
{
- // currently not supported
- return -1;
+ // currently only supported on peripheral role
+ return remoteMtu;
}
QT_END_NAMESPACE
diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus_p.h b/src/bluetooth/qlowenergycontroller_bluezdbus_p.h
index 8cfcebc9..afa2e8f3 100644
--- a/src/bluetooth/qlowenergycontroller_bluezdbus_p.h
+++ b/src/bluetooth/qlowenergycontroller_bluezdbus_p.h
@@ -33,13 +33,15 @@ class OrgFreedesktopDBusPropertiesInterface;
QT_BEGIN_NAMESPACE
+class QtBluezPeripheralApplication;
+class QtBluezPeripheralConnectionManager;
class QDBusPendingCallWatcher;
class QLowEnergyControllerPrivateBluezDBus final : public QLowEnergyControllerPrivate
{
Q_OBJECT
public:
- QLowEnergyControllerPrivateBluezDBus();
+ QLowEnergyControllerPrivateBluezDBus(const QString& adapterPathWithPeripheralSupport = {});
~QLowEnergyControllerPrivateBluezDBus() override;
void init() override;
@@ -102,15 +104,29 @@ private slots:
void onCharWriteFinished(QDBusPendingCallWatcher *call);
void onDescWriteFinished(QDBusPendingCallWatcher *call);
private:
+
OrgBluezAdapter1Interface* adapter{};
OrgBluezDevice1Interface* device{};
OrgFreedesktopDBusObjectManagerInterface* managerBluez{};
OrgFreedesktopDBusPropertiesInterface* deviceMonitor{};
+ QString adapterPathWithPeripheralSupport;
+ int remoteMtu{-1};
+ QtBluezPeripheralApplication* peripheralApplication{};
+ QtBluezPeripheralConnectionManager* peripheralConnectionManager{};
QLeDBusAdvertiser *advertiser{};
void handleAdvertisingError();
- void handleAdvertisingStopped();
-
+ void handlePeripheralApplicationError();
+ void handlePeripheralApplicationRegistered();
+ void handlePeripheralConnectivityChanged(bool connected);
+ void handlePeripheralCharacteristicValueUpdate(QLowEnergyHandle handle,
+ const QByteArray& value);
+ void handlePeripheralDescriptorValueUpdate(QLowEnergyHandle characteristicHandle,
+ QLowEnergyHandle descriptorHandle,
+ const QByteArray& value);
+ void handlePeripheralRemoteDeviceChanged(const QBluetoothAddress& address,
+ const QString& name,
+ quint16 mtu);
bool pendingConnect = false;
bool disconnectSignalRequired = false;