// 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; using namespace QtBluetoothPrivate; // for D-Bus wrappers 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 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. Similarly the extended properties // descriptor is generated by Bluez if the related flags are set. Using the application // provided descriptors would result in duplicate descriptors. if (descriptorData.uuid() == QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration || descriptorData.uuid() == QBluetoothUuid::DescriptorType::CharacteristicExtendedProperties) { 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 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 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"