Revision control

Copy as Markdown

// 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<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
const auto includedServices = serviceData.includedServices();
for (const auto includedService : 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};
const auto characteristics = serviceData.characteristics();
for (const auto &characteristicData : 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};
const auto descriptors = characteristicData.descriptors();
for (const auto &descriptorData : 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<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"