diff options
Diffstat (limited to 'src/bluetooth/bluez/bluezperipheralapplication.cpp')
-rw-r--r-- | src/bluetooth/bluez/bluezperipheralapplication.cpp | 276 |
1 files changed, 276 insertions, 0 deletions
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" |