Revision control
Copy as Markdown
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qlowenergycontroller_bluezdbus_p.h"
#include "bluez/adapter1_bluez5_p.h"
#include "bluez/bluez5_helper_p.h"
#include "bluez/device1_bluez5_p.h"
#include "bluez/gattservice1_p.h"
#include "bluez/gattchar1_p.h"
#include "bluez/gattdesc1_p.h"
#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)
using namespace QtBluetoothPrivate; // for D-Bus wrappers
QLowEnergyControllerPrivateBluezDBus::QLowEnergyControllerPrivateBluezDBus(
const QString &adapterPathWithPeripheralSupport)
: QLowEnergyControllerPrivate(),
adapterPathWithPeripheralSupport(adapterPathWithPeripheralSupport)
{
}
QLowEnergyControllerPrivateBluezDBus::~QLowEnergyControllerPrivateBluezDBus()
{
if (state != QLowEnergyController::UnconnectedState) {
qCWarning(QT_BT_BLUEZ) << "Low Energy Controller is not Unconnected when deleted."
<< "Deleted in state:" << state;
}
}
void QLowEnergyControllerPrivateBluezDBus::init()
{
if (role == QLowEnergyController::PeripheralRole) {
Q_ASSERT(!adapterPathWithPeripheralSupport.isEmpty());
peripheralApplication = new QtBluezPeripheralApplication(adapterPathWithPeripheralSupport,
this);
QObject::connect(peripheralApplication, &QtBluezPeripheralApplication::errorOccurred, this,
&QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationError);
QObject::connect(peripheralApplication, &QtBluezPeripheralApplication::registered, this,
&QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationRegistered);
QObject::connect(peripheralApplication,
&QtBluezPeripheralApplication::characteristicValueUpdatedByRemote, this,
&QLowEnergyControllerPrivateBluezDBus::handlePeripheralCharacteristicValueUpdate);
QObject::connect(peripheralApplication,
&QtBluezPeripheralApplication::descriptorValueUpdatedByRemote, this,
&QLowEnergyControllerPrivateBluezDBus::handlePeripheralDescriptorValueUpdate);
peripheralConnectionManager =
new QtBluezPeripheralConnectionManager(localAdapter, this);
QObject::connect(peripheralApplication,
&QtBluezPeripheralApplication::remoteDeviceAccessEvent,
peripheralConnectionManager,
&QtBluezPeripheralConnectionManager::remoteDeviceAccessEvent);
QObject::connect(peripheralConnectionManager,
&QtBluezPeripheralConnectionManager::connectivityStateChanged, this,
&QLowEnergyControllerPrivateBluezDBus::handlePeripheralConnectivityChanged);
QObject::connect(peripheralConnectionManager,
&QtBluezPeripheralConnectionManager::remoteDeviceChanged, this,
&QLowEnergyControllerPrivateBluezDBus::handlePeripheralRemoteDeviceChanged);
}
}
void QLowEnergyControllerPrivateBluezDBus::devicePropertiesChanged(
const QString &interface, const QVariantMap &changedProperties,
const QStringList &/*removedProperties*/)
{
if (interface == QStringLiteral("org.bluez.Device1")) {
qCDebug(QT_BT_BLUEZ) << "######" << interface << changedProperties;
if (changedProperties.contains(QStringLiteral("ServicesResolved"))) {
//we could check for Connected property as well, but we choose to wait
//for ServicesResolved being true
if (pendingConnect) {
bool isResolved = changedProperties.value(QStringLiteral("ServicesResolved")).toBool();
if (isResolved) {
setState(QLowEnergyController::ConnectedState);
pendingConnect = false;
disconnectSignalRequired = true;
Q_Q(QLowEnergyController);
emit q->connected();
}
}
}
if (changedProperties.contains(QStringLiteral("Connected"))) {
bool isConnected = changedProperties.value(QStringLiteral("Connected")).toBool();
if (!isConnected) {
switch (state) {
case QLowEnergyController::ConnectingState:
case QLowEnergyController::ConnectedState:
case QLowEnergyController::DiscoveringState:
case QLowEnergyController::DiscoveredState:
case QLowEnergyController::ClosingState:
{
QLowEnergyController::Error newError = QLowEnergyController::NoError;
if (pendingConnect)
newError = QLowEnergyController::ConnectionError;
executeClose(newError);
}
break;
case QLowEnergyController::AdvertisingState:
case QLowEnergyController::UnconnectedState:
//ignore
break;
}
}
}
if (changedProperties.contains(QStringLiteral("UUIDs"))) {
const QStringList newUuidStringList = changedProperties.value(QStringLiteral("UUIDs")).toStringList();
QList<QBluetoothUuid> newUuidList;
for (const QString &uuidString : newUuidStringList)
newUuidList.append(QBluetoothUuid(uuidString));
const auto serviceKeys = serviceList.keys();
for (const QBluetoothUuid &uuid : serviceKeys) {
if (!newUuidList.contains(uuid)) {
qCDebug(QT_BT_BLUEZ) << __func__ << "Service" << uuid << "has been removed";
QSharedPointer<QLowEnergyServicePrivate> service = serviceList.take(uuid);
service->setController(nullptr);
dbusServices.remove(uuid);
}
}
}
} else if (interface == QStringLiteral("org.bluez.Battery1")) {
qCDebug(QT_BT_BLUEZ) << "######" << interface << changedProperties;
if (changedProperties.contains(QStringLiteral("Percentage"))) {
// if battery service is discovered and ClientCharConfig is enabled
// emit characteristicChanged() signal
const QBluetoothUuid uuid(QBluetoothUuid::ServiceClassUuid::BatteryService);
if (!serviceList.contains(uuid) || !dbusServices.contains(uuid)
|| !dbusServices[uuid].hasBatteryService
|| dbusServices[uuid].batteryInterface.isNull())
return;
QSharedPointer<QLowEnergyServicePrivate> serviceData = serviceList.value(uuid);
if (serviceData->state != QLowEnergyService::RemoteServiceDiscovered)
return;
QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData>::iterator iter;
iter = serviceData->characteristicList.begin();
while (iter != serviceData->characteristicList.end()) {
auto &charData = iter.value();
if (charData.uuid != QBluetoothUuid::CharacteristicType::BatteryLevel)
continue;
// Client Characteristic Notification enabled?
bool cccActive = false;
for (const QLowEnergyServicePrivate::DescData &descData : std::as_const(charData.descriptorList)) {
if (descData.uuid != QBluetoothUuid(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration))
continue;
if (descData.value == QByteArray::fromHex("0100")
|| descData.value == QByteArray::fromHex("0200")) {
cccActive = true;
break;
}
}
const QByteArray newValue(1, char(dbusServices[uuid].batteryInterface->percentage()));
qCDebug(QT_BT_BLUEZ) << "Battery1 char update" << cccActive
<< charData.value.toHex() << "->" << newValue.toHex();
if (cccActive && newValue != charData.value) {
qCDebug(QT_BT_BLUEZ) << "Property update for Battery1";
charData.value = newValue;
QLowEnergyCharacteristic ch(serviceData, iter.key());
emit serviceData->characteristicChanged(ch, newValue);
}
break;
}
}
}
}
void QLowEnergyControllerPrivateBluezDBus::characteristicPropertiesChanged(
QLowEnergyHandle charHandle, const QString &interface,
const QVariantMap &changedProperties,
const QStringList &/*removedProperties*/)
{
//qCDebug(QT_BT_BLUEZ) << "$$$$$$$$$$$$$$$$$$ char monitor"
// << interface << changedProperties << charHandle;
if (interface != QStringLiteral("org.bluez.GattCharacteristic1"))
return;
if (!changedProperties.contains(QStringLiteral("Value")))
return;
const QLowEnergyCharacteristic changedChar = characteristicForHandle(charHandle);
const QLowEnergyDescriptor ccnDescriptor = changedChar.descriptor(
QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);
if (!ccnDescriptor.isValid())
return;
const QByteArray newValue = changedProperties.value(QStringLiteral("Value")).toByteArray();
if (changedChar.properties() & QLowEnergyCharacteristic::Read)
updateValueOfCharacteristic(charHandle, newValue, false); //TODO upgrade to NEW_VALUE/APPEND_VALUE
auto service = serviceForHandle(charHandle);
if (!service.isNull())
emit service->characteristicChanged(changedChar, newValue);
}
void QLowEnergyControllerPrivateBluezDBus::interfacesRemoved(const QDBusObjectPath &objectPath,
const QStringList &interfaces)
{
if (objectPath.path() == device->path()) {
if (interfaces.contains(QStringLiteral("org.bluez.Device1"))) {
qCWarning(QT_BT_BLUEZ) << "DBus Device1 was removed";
executeClose(QLowEnergyController::UnknownRemoteDeviceError);
} else {
qCDebug(QT_BT_BLUEZ) << "DBus interfaces" << interfaces << "were removed from"
<< objectPath.path();
}
} else if (objectPath.path() == adapter->path()) {
qCWarning(QT_BT_BLUEZ) << "DBus Adapter was removed";
executeClose(QLowEnergyController::InvalidBluetoothAdapterError);
}
}
void QLowEnergyControllerPrivateBluezDBus::resetController()
{
if (managerBluez) {
delete managerBluez;
managerBluez = nullptr;
}
if (adapter) {
delete adapter;
adapter = nullptr;
}
if (device) {
delete device;
device = nullptr;
}
if (deviceMonitor) {
delete deviceMonitor;
deviceMonitor = nullptr;
}
if (advertiser) {
delete advertiser;
advertiser = nullptr;
}
if (peripheralApplication)
peripheralApplication->reset();
if (peripheralConnectionManager)
peripheralConnectionManager->reset();
remoteName.clear();
remoteMtu = -1;
dbusServices.clear();
jobs.clear();
invalidateServices();
pendingConnect = disconnectSignalRequired = false;
jobPending = false;
}
void QLowEnergyControllerPrivateBluezDBus::connectToDeviceHelper()
{
resetController();
bool ok = false;
const QString hostAdapterPath = findAdapterForAddress(localAdapter, &ok);
if (!ok || hostAdapterPath.isEmpty()) {
qCWarning(QT_BT_BLUEZ) << "Cannot find suitable bluetooth adapter";
setError(QLowEnergyController::InvalidBluetoothAdapterError);
return;
}
auto manager = std::make_unique<OrgFreedesktopDBusObjectManagerInterface>(
QStringLiteral("org.bluez"), QStringLiteral("/"), QDBusConnection::systemBus());
QDBusPendingReply<ManagedObjectList> reply = manager->GetManagedObjects();
reply.waitForFinished();
if (reply.isError()) {
qCWarning(QT_BT_BLUEZ) << "Cannot enumerate Bluetooth devices for GATT connect";
setError(QLowEnergyController::ConnectionError);
return;
}
QString devicePath;
ManagedObjectList managedObjectList = reply.value();
for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
const InterfaceList &ifaceList = it.value();
for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
const QString &iface = jt.key();
const QVariantMap &ifaceValues = jt.value();
if (iface == QStringLiteral("org.bluez.Device1")) {
if (remoteDevice.toString() == ifaceValues.value(QStringLiteral("Address")).toString())
{
const QVariant adapterForCurrentDevice = ifaceValues.value(QStringLiteral("Adapter"));
if (qvariant_cast<QDBusObjectPath>(adapterForCurrentDevice).path() == hostAdapterPath) {
devicePath = it.key().path();
break;
}
}
}
}
if (!devicePath.isEmpty())
break;
}
if (devicePath.isEmpty()) {
qCDebug(QT_BT_BLUEZ) << "Cannot find targeted remote device. "
"Re-running device discovery might help";
setError(QLowEnergyController::UnknownRemoteDeviceError);
return;
}
managerBluez = manager.release();
connect(managerBluez, &OrgFreedesktopDBusObjectManagerInterface::InterfacesRemoved,
this, &QLowEnergyControllerPrivateBluezDBus::interfacesRemoved);
adapter = new OrgBluezAdapter1Interface(
QStringLiteral("org.bluez"), hostAdapterPath,
QDBusConnection::systemBus(), this);
device = new OrgBluezDevice1Interface(
QStringLiteral("org.bluez"), devicePath,
QDBusConnection::systemBus(), this);
deviceMonitor = new OrgFreedesktopDBusPropertiesInterface(
QStringLiteral("org.bluez"), devicePath,
QDBusConnection::systemBus(), this);
connect(deviceMonitor, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged,
this, &QLowEnergyControllerPrivateBluezDBus::devicePropertiesChanged);
}
void QLowEnergyControllerPrivateBluezDBus::connectToDevice()
{
qCDebug(QT_BT_BLUEZ) << "QLowEnergyControllerPrivateBluezDBus::connectToDevice()";
connectToDeviceHelper();
if (!adapter || !device)
return;
if (!adapter->powered()) {
qCWarning(QT_BT_BLUEZ) << "Error: Local adapter is powered off";
setError(QLowEnergyController::ConnectionError);
return;
}
setState(QLowEnergyController::ConnectingState);
//Bluez interface is shared among all platform processes
//and hence we might be connected already
if (device->connected() && device->servicesResolved()) {
//connectToDevice is noop
disconnectSignalRequired = true;
setState(QLowEnergyController::ConnectedState);
Q_Q(QLowEnergyController);
emit q->connected();
return;
}
pendingConnect = true;
QDBusPendingReply<> reply = device->Connect();
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::connect() failed"
<< reply.reply().errorName()
<< reply.reply().errorMessage();
executeClose(QLowEnergyController::UnknownError);
} // else -> connected when Connected property is set to true (see devicePropertiesChanged())
call->deleteLater();
});
}
void QLowEnergyControllerPrivateBluezDBus::disconnectFromDevice()
{
if (role == QLowEnergyController::CentralRole) {
if (!device)
return;
setState(QLowEnergyController::ClosingState);
QDBusPendingReply<> reply = device->Disconnect();
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this,
[this](QDBusPendingCallWatcher* call) {
QDBusPendingReply<> reply = *call;
if (reply.isError()) {
qCDebug(QT_BT_BLUEZ) << "BTLE_DBUS::disconnect() failed"
<< reply.reply().errorName()
<< reply.reply().errorMessage();
executeClose(QLowEnergyController::UnknownError);
} else {
executeClose(QLowEnergyController::NoError);
}
call->deleteLater();
});
} else {
Q_Q(QLowEnergyController);
peripheralConnectionManager->disconnectDevices();
resetController();
remoteDevice.clear();
const auto emitDisconnected = (state == QLowEnergyController::ConnectedState);
setState(QLowEnergyController::UnconnectedState);
if (emitDisconnected)
emit q->disconnected();
}
}
void QLowEnergyControllerPrivateBluezDBus::discoverServices()
{
QDBusPendingReply<ManagedObjectList> reply = managerBluez->GetManagedObjects();
reply.waitForFinished();
if (reply.isError()) {
qCWarning(QT_BT_BLUEZ) << "Cannot discover services";
setError(QLowEnergyController::UnknownError);
setState(QLowEnergyController::DiscoveredState);
return;
}
Q_Q(QLowEnergyController);
auto setupServicePrivate = [&, q](
QLowEnergyService::ServiceType type, const QBluetoothUuid &uuid,
const QString &path, const bool battery1Interface = false){
QSharedPointer<QLowEnergyServicePrivate> priv = QSharedPointer<QLowEnergyServicePrivate>::create();
priv->uuid = uuid;
priv->type = type; // we make a guess we cannot validate
priv->setController(this);
GattService serviceContainer;
serviceContainer.servicePath = path;
if (battery1Interface) {
qCDebug(QT_BT_BLUEZ) << "Using Battery1 interface to emulate generic interface";
serviceContainer.hasBatteryService = true;
}
serviceList.insert(priv->uuid, priv);
dbusServices.insert(priv->uuid, serviceContainer);
emit q->serviceDiscovered(priv->uuid);
};
const ManagedObjectList managedObjectList = reply.value();
const QString servicePathPrefix = device->path().append(QStringLiteral("/service"));
// The Bluez battery service (0x180f) support has evolved over time and needs additional logic:
//
// * Until 5.47 Bluez exposes battery services via generic interface (GattService1) only
// * Between 5.48..5.54 Bluez exposes battery service as a dedicated 'Battery1' interface only
// * From 5.55 Bluez exposes both the generic service as well as the dedicated 'Battery1'
//
// To hide the difference from users the 'Battery1' interface will be used to emulate the
// generic interface. Importantly also the GattService1 interface, if available, is available
// early whereas the Battery1 interface may be available only later (generated too late for
// this service discovery's purposes)
//
// The precedence for battery service here is as follows:
// * If available via GattService1, use that and ignore possible Battery1 interface
// * If Battery1 interface is available or the 'org.bluez.Device1' lists battery service
// amongst list of available services, mark the service such that the code will later
// look up the Battery1 service details
bool gattBatteryService{false};
QString batteryServicePath;
for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
const InterfaceList &ifaceList = it.value();
if (!it.key().path().startsWith(device->path()))
continue;
if (it.key().path() == device->path()) {
// See if the battery service is available or is assumed to be available later
for (InterfaceList::const_iterator battIter = ifaceList.constBegin(); battIter != ifaceList.constEnd(); ++battIter) {
const QString &iface = battIter.key();
if (iface == QStringLiteral("org.bluez.Battery1")) {
qCDebug(QT_BT_BLUEZ) << "Dedicated Battery1 service available";
batteryServicePath = it.key().path();
break;
} else if (iface == QStringLiteral("org.bluez.Device1")) {
const auto batteryUuids =
battIter.value()[QStringLiteral("UUIDs")].toStringList();
for (auto const &uuid : batteryUuids) {
if (QBluetoothUuid(uuid) ==
QBluetoothUuid::ServiceClassUuid::BatteryService) {
qCDebug(QT_BT_BLUEZ) << "Battery service listed as available service";
batteryServicePath = it.key().path();
break;
}
}
}
}
continue;
}
if (!it.key().path().startsWith(servicePathPrefix))
continue;
for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
const QString &iface = jt.key();
if (iface == QStringLiteral("org.bluez.GattService1")) {
QScopedPointer<OrgBluezGattService1Interface> service(new OrgBluezGattService1Interface(
QStringLiteral("org.bluez"),it.key().path(),
QDBusConnection::systemBus(), this));
if (QBluetoothUuid(service->uUID()) ==
QBluetoothUuid::ServiceClassUuid::BatteryService) {
qCDebug(QT_BT_BLUEZ) << "Using battery service via GattService1 interface";
gattBatteryService = true;
}
setupServicePrivate(service->primary()
? QLowEnergyService::PrimaryService
: QLowEnergyService::IncludedService,
QBluetoothUuid(service->uUID()), it.key().path());
}
}
}
if (!gattBatteryService && !batteryServicePath.isEmpty()) {
setupServicePrivate(QLowEnergyService::PrimaryService,
QBluetoothUuid::ServiceClassUuid::BatteryService,
batteryServicePath, true);
}
setState(QLowEnergyController::DiscoveredState);
emit q->discoveryFinished();
}
void QLowEnergyControllerPrivateBluezDBus::discoverBatteryServiceDetails(
GattService &dbusData, QSharedPointer<QLowEnergyServicePrivate> serviceData)
{
// This process exists to work around the fact that Battery services (0x180f)
// are not mapped as generic services but use the Battery1 interface.
// Artificial chararacteristics and descriptors are created to emulate the generic behavior.
auto batteryService = QSharedPointer<OrgBluezBattery1Interface>::create(
QStringLiteral("org.bluez"), dbusData.servicePath,
QDBusConnection::systemBus());
dbusData.batteryInterface = batteryService;
serviceData->startHandle = runningHandle++; //service start handle
// Create BatteryLevel char
QLowEnergyHandle indexHandle = runningHandle++; // char handle index
QLowEnergyServicePrivate::CharData charData;
charData.valueHandle = runningHandle++;
charData.properties.setFlag(QLowEnergyCharacteristic::Read);
charData.properties.setFlag(QLowEnergyCharacteristic::Notify);
charData.uuid = QBluetoothUuid::CharacteristicType::BatteryLevel;
charData.value = QByteArray(1, char(batteryService->percentage()));
// Create the descriptors for the BatteryLevel
// They are hardcoded although CCC may change
QLowEnergyServicePrivate::DescData descData;
QLowEnergyHandle descriptorHandle = runningHandle++;
descData.uuid = QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration;
descData.value = QByteArray::fromHex("0000"); // all configs off
charData.descriptorList.insert(descriptorHandle, descData);
descriptorHandle = runningHandle++;
descData.uuid = QBluetoothUuid::DescriptorType::CharacteristicPresentationFormat;
//for details see Characteristic Presentation Format Vol3, Part G 3.3.3.5
// unsigend 8 bit, exp=1, org.bluetooth.unit.percentage, namespace & description
// bit order: little endian
descData.value = QByteArray::fromHex("0400ad27011131");
charData.descriptorList.insert(descriptorHandle, descData);
descriptorHandle = runningHandle++;
descData.uuid = QBluetoothUuid::DescriptorType::ReportReference;
descData.value = QByteArray::fromHex("0401");
charData.descriptorList.insert(descriptorHandle, descData);
serviceData->characteristicList[indexHandle] = charData;
serviceData->endHandle = runningHandle++;
serviceData->setState(QLowEnergyService::RemoteServiceDiscovered);
}
void QLowEnergyControllerPrivateBluezDBus::executeClose(QLowEnergyController::Error newError)
{
const bool emitDisconnect = disconnectSignalRequired;
resetController();
if (newError != QLowEnergyController::NoError)
setError(newError);
setState(QLowEnergyController::UnconnectedState);
if (emitDisconnect) {
Q_Q(QLowEnergyController);
emit q->disconnected();
}
}
void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(
const QBluetoothUuid &service, QLowEnergyService::DiscoveryMode mode)
{
if (!serviceList.contains(service) || !dbusServices.contains(service)) {
qCWarning(QT_BT_BLUEZ) << "Discovery of unknown service" << service.toString()
<< "not possible";
return;
}
//clear existing service data and run new discovery
QSharedPointer<QLowEnergyServicePrivate> serviceData = serviceList.value(service);
serviceData->characteristicList.clear();
GattService &dbusData = dbusServices[service];
dbusData.characteristics.clear();
if (dbusData.hasBatteryService) {
qCDebug(QT_BT_BLUEZ) << "Triggering Battery1 service discovery on " << dbusData.servicePath;
discoverBatteryServiceDetails(dbusData, serviceData);
return;
}
QDBusPendingReply<ManagedObjectList> reply = managerBluez->GetManagedObjects();
reply.waitForFinished();
if (reply.isError()) {
qCWarning(QT_BT_BLUEZ) << "Cannot discover services";
setError(QLowEnergyController::UnknownError);
setState(QLowEnergyController::DiscoveredState);
return;
}
const ManagedObjectList managedObjectList = reply.value();
for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
const InterfaceList &ifaceList = it.value();
if (!it.key().path().startsWith(dbusData.servicePath))
continue;
for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
const QString &iface = jt.key();
if (iface == QStringLiteral("org.bluez.GattCharacteristic1")) {
auto charInterface = QSharedPointer<OrgBluezGattCharacteristic1Interface>::create(
QStringLiteral("org.bluez"), it.key().path(),
QDBusConnection::systemBus());
GattCharacteristic dbusCharData;
dbusCharData.characteristic = charInterface;
dbusData.characteristics.append(dbusCharData);
} else if (iface == QStringLiteral("org.bluez.GattDescriptor1")) {
auto descInterface = QSharedPointer<OrgBluezGattDescriptor1Interface>::create(
QStringLiteral("org.bluez"), it.key().path(),
QDBusConnection::systemBus());
bool found = false;
for (GattCharacteristic &dbusCharData : dbusData.characteristics) {
if (!descInterface->path().startsWith(
dbusCharData.characteristic->path()))
continue;
found = true;
dbusCharData.descriptors.append(descInterface);
break;
}
Q_ASSERT(found);
if (!found)
qCWarning(QT_BT_BLUEZ) << "Descriptor discovery error";
}
}
}
//populate servicePrivate based on dbus data
serviceData->startHandle = runningHandle++;
for (GattCharacteristic &dbusChar : dbusData.characteristics) {
const QLowEnergyHandle indexHandle = runningHandle++;
QLowEnergyServicePrivate::CharData charData;
// characteristic data
charData.valueHandle = runningHandle++;
const QStringList properties = dbusChar.characteristic->flags();
for (const auto &entry : properties) {
if (entry == QStringLiteral("broadcast"))
charData.properties.setFlag(QLowEnergyCharacteristic::Broadcasting, true);
else if (entry == QStringLiteral("read"))
charData.properties.setFlag(QLowEnergyCharacteristic::Read, true);
else if (entry == QStringLiteral("write-without-response"))
charData.properties.setFlag(QLowEnergyCharacteristic::WriteNoResponse, true);
else if (entry == QStringLiteral("write"))
charData.properties.setFlag(QLowEnergyCharacteristic::Write, true);
else if (entry == QStringLiteral("notify"))
charData.properties.setFlag(QLowEnergyCharacteristic::Notify, true);
else if (entry == QStringLiteral("indicate"))
charData.properties.setFlag(QLowEnergyCharacteristic::Indicate, true);
else if (entry == QStringLiteral("authenticated-signed-writes"))
charData.properties.setFlag(QLowEnergyCharacteristic::WriteSigned, true);
else if (entry == QStringLiteral("reliable-write"))
charData.properties.setFlag(QLowEnergyCharacteristic::ExtendedProperty, true);
else if (entry == QStringLiteral("writable-auxiliaries"))
charData.properties.setFlag(QLowEnergyCharacteristic::ExtendedProperty, true);
//all others ignored - not relevant for this API
}
charData.uuid = QBluetoothUuid(dbusChar.characteristic->uUID());
// schedule read for initial char value
if (mode == QLowEnergyService::FullDiscovery
&& charData.properties.testFlag(QLowEnergyCharacteristic::Read)) {
GattJob job;
job.flags = GattJob::JobFlags({GattJob::CharRead, GattJob::ServiceDiscovery});
job.service = serviceData;
job.handle = indexHandle;
jobs.append(job);
}
// descriptor data
for (const auto &descEntry : std::as_const(dbusChar.descriptors)) {
const QLowEnergyHandle descriptorHandle = runningHandle++;
QLowEnergyServicePrivate::DescData descData;
descData.uuid = QBluetoothUuid(descEntry->uUID());
charData.descriptorList.insert(descriptorHandle, descData);
// every ClientCharacteristicConfiguration needs to track property changes
if (descData.uuid
== QBluetoothUuid(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration)) {
dbusChar.charMonitor = QSharedPointer<OrgFreedesktopDBusPropertiesInterface>::create(
QStringLiteral("org.bluez"),
dbusChar.characteristic->path(),
QDBusConnection::systemBus(), this);
connect(dbusChar.charMonitor.data(), &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged,
this, [this, indexHandle](const QString &interface, const QVariantMap &changedProperties,
const QStringList &removedProperties) {
characteristicPropertiesChanged(indexHandle, interface,
changedProperties, removedProperties);
});
}
if (mode == QLowEnergyService::FullDiscovery) {
// schedule read for initial descriptor value
GattJob job;
job.flags = GattJob::JobFlags({ GattJob::DescRead, GattJob::ServiceDiscovery });
job.service = serviceData;
job.handle = descriptorHandle;
jobs.append(job);
}
}
serviceData->characteristicList[indexHandle] = charData;
}
serviceData->endHandle = runningHandle++;
// last job is last step of service discovery
if (!jobs.isEmpty()) {
GattJob &lastJob = jobs.last();
lastJob.flags.setFlag(GattJob::LastServiceDiscovery, true);
} else {
serviceData->setState(QLowEnergyService::RemoteServiceDiscovered);
}
scheduleNextJob();
}
void QLowEnergyControllerPrivateBluezDBus::prepareNextJob()
{
jobs.takeFirst(); // finish last job
jobPending = false;
scheduleNextJob(); // continue with next job - if available
}
void QLowEnergyControllerPrivateBluezDBus::onCharReadFinished(QDBusPendingCallWatcher *call)
{
if (!jobPending || jobs.isEmpty()) {
// this may happen when service disconnects before dbus watcher returns later on
qCWarning(QT_BT_BLUEZ) << "Aborting onCharReadFinished due to disconnect";
Q_ASSERT(state == QLowEnergyController::UnconnectedState);
return;
}
const GattJob nextJob = jobs.constFirst();
Q_ASSERT(nextJob.flags.testFlag(GattJob::CharRead));
QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(nextJob.handle);
if (service.isNull() || !dbusServices.contains(service->uuid)) {
qCWarning(QT_BT_BLUEZ) << "onCharReadFinished: Invalid GATT job. Skipping.";
call->deleteLater();
prepareNextJob();
return;
}
const QLowEnergyServicePrivate::CharData &charData =
service->characteristicList.value(nextJob.handle);
bool isServiceDiscovery = nextJob.flags.testFlag(GattJob::ServiceDiscovery);
QDBusPendingReply<QByteArray> reply = *call;
if (reply.isError()) {
qCWarning(QT_BT_BLUEZ) << "Cannot initiate reading of" << charData.uuid
<< "of service" << service->uuid
<< reply.error().name() << reply.error().message();
if (!isServiceDiscovery)
service->setError(QLowEnergyService::CharacteristicReadError);
} else {
qCDebug(QT_BT_BLUEZ) << "Read Char:" << charData.uuid << reply.value().toHex();
if (charData.properties.testFlag(QLowEnergyCharacteristic::Read))
updateValueOfCharacteristic(nextJob.handle, reply.value(), false);
if (isServiceDiscovery) {
if (nextJob.flags.testFlag(GattJob::LastServiceDiscovery))
service->setState(QLowEnergyService::RemoteServiceDiscovered);
} else {
QLowEnergyCharacteristic ch(service, nextJob.handle);
emit service->characteristicRead(ch, reply.value());
}
}
call->deleteLater();
prepareNextJob();
}
void QLowEnergyControllerPrivateBluezDBus::onDescReadFinished(QDBusPendingCallWatcher *call)
{
if (!jobPending || jobs.isEmpty()) {
// this may happen when service disconnects before dbus watcher returns later on
qCWarning(QT_BT_BLUEZ) << "Aborting onDescReadFinished due to disconnect";
Q_ASSERT(state == QLowEnergyController::UnconnectedState);
return;
}
const GattJob nextJob = jobs.constFirst();
Q_ASSERT(nextJob.flags.testFlag(GattJob::DescRead));
QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(nextJob.handle);
if (service.isNull() || !dbusServices.contains(service->uuid)) {
qCWarning(QT_BT_BLUEZ) << "onDescReadFinished: Invalid GATT job. Skipping.";
call->deleteLater();
prepareNextJob();
return;
}
QLowEnergyCharacteristic ch = characteristicForHandle(nextJob.handle);
if (!ch.isValid()) {
qCWarning(QT_BT_BLUEZ) << "Cannot find char for desc read (onDescReadFinished 1).";
call->deleteLater();
prepareNextJob();
return;
}
const QLowEnergyServicePrivate::CharData &charData =
service->characteristicList.value(ch.attributeHandle());
if (!charData.descriptorList.contains(nextJob.handle)) {
qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor (onDescReadFinished 2).";
call->deleteLater();
prepareNextJob();
return;
}
bool isServiceDiscovery = nextJob.flags.testFlag(GattJob::ServiceDiscovery);
QDBusPendingReply<QByteArray> reply = *call;
if (reply.isError()) {
qCWarning(QT_BT_BLUEZ) << "Cannot read descriptor (onDescReadFinished 3): "
<< charData.descriptorList[nextJob.handle].uuid
<< charData.uuid
<< reply.error().name() << reply.error().message();
if (!isServiceDiscovery)
service->setError(QLowEnergyService::DescriptorReadError);
} else {
qCDebug(QT_BT_BLUEZ) << "Read Desc:" << reply.value();
updateValueOfDescriptor(ch.attributeHandle(), nextJob.handle, reply.value(), false);
if (isServiceDiscovery) {
if (nextJob.flags.testFlag(GattJob::LastServiceDiscovery))
service->setState(QLowEnergyService::RemoteServiceDiscovered);
} else {
QLowEnergyDescriptor desc(service, ch.attributeHandle(), nextJob.handle);
emit service->descriptorRead(desc, reply.value());
}
}
call->deleteLater();
prepareNextJob();
}
void QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished(QDBusPendingCallWatcher *call)
{
if (!jobPending || jobs.isEmpty()) {
// this may happen when service disconnects before dbus watcher returns later on
qCWarning(QT_BT_BLUEZ) << "Aborting onCharWriteFinished due to disconnect";
Q_ASSERT(state == QLowEnergyController::UnconnectedState);
return;
}
const GattJob nextJob = jobs.constFirst();
Q_ASSERT(nextJob.flags.testFlag(GattJob::CharWrite));
QSharedPointer<QLowEnergyServicePrivate> service = nextJob.service;
if (!dbusServices.contains(service->uuid)) {
qCWarning(QT_BT_BLUEZ) << "onCharWriteFinished: Invalid GATT job. Skipping.";
call->deleteLater();
prepareNextJob();
return;
}
const QLowEnergyServicePrivate::CharData &charData =
service->characteristicList.value(nextJob.handle);
QDBusPendingReply<> reply = *call;
if (reply.isError()) {
qCWarning(QT_BT_BLUEZ) << "Cannot initiate writing of" << charData.uuid
<< "of service" << service->uuid
<< reply.error().name() << reply.error().message();
service->setError(QLowEnergyService::CharacteristicWriteError);
} else {
if (charData.properties.testFlag(QLowEnergyCharacteristic::Read))
updateValueOfCharacteristic(nextJob.handle, nextJob.value, false);
QLowEnergyCharacteristic ch(service, nextJob.handle);
// write without response implies zero feedback
if (nextJob.writeMode == QLowEnergyService::WriteWithResponse) {
qCDebug(QT_BT_BLUEZ) << "Written Char:" << charData.uuid << nextJob.value.toHex();
emit service->characteristicWritten(ch, nextJob.value);
}
}
call->deleteLater();
prepareNextJob();
}
void QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished(QDBusPendingCallWatcher *call)
{
if (!jobPending || jobs.isEmpty()) {
// this may happen when service disconnects before dbus watcher returns later on
qCWarning(QT_BT_BLUEZ) << "Aborting onDescWriteFinished due to disconnect";
Q_ASSERT(state == QLowEnergyController::UnconnectedState);
return;
}
const GattJob nextJob = jobs.constFirst();
Q_ASSERT(nextJob.flags.testFlag(GattJob::DescWrite));
QSharedPointer<QLowEnergyServicePrivate> service = nextJob.service;
if (!dbusServices.contains(service->uuid)) {
qCWarning(QT_BT_BLUEZ) << "onDescWriteFinished: Invalid GATT job. Skipping.";
call->deleteLater();
prepareNextJob();
return;
}
const QLowEnergyCharacteristic associatedChar = characteristicForHandle(nextJob.handle);
const QLowEnergyDescriptor descriptor = descriptorForHandle(nextJob.handle);
if (!associatedChar.isValid() || !descriptor.isValid()) {
qCWarning(QT_BT_BLUEZ) << "onDescWriteFinished: Cannot find associated char/desc: "
<< associatedChar.isValid();
call->deleteLater();
prepareNextJob();
return;
}
QDBusPendingReply<> reply = *call;
if (reply.isError()) {
qCWarning(QT_BT_BLUEZ) << "Cannot initiate writing of" << descriptor.uuid()
<< "of char" << associatedChar.uuid()
<< "of service" << service->uuid
<< reply.error().name() << reply.error().message();
service->setError(QLowEnergyService::DescriptorWriteError);
} else {
qCDebug(QT_BT_BLUEZ) << "Write Desc:" << descriptor.uuid() << nextJob.value.toHex();
updateValueOfDescriptor(associatedChar.attributeHandle(), nextJob.handle,
nextJob.value, false);
emit service->descriptorWritten(descriptor, nextJob.value);
}
call->deleteLater();
prepareNextJob();
}
void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob()
{
if (jobPending || jobs.isEmpty())
return;
jobPending = true;
const GattJob nextJob = jobs.constFirst();
QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(nextJob.handle);
if (service.isNull() || !dbusServices.contains(service->uuid)) {
qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleNextJob). Skipping.";
prepareNextJob();
return;
}
const GattService &dbusServiceData = dbusServices[service->uuid];
if (nextJob.flags.testFlag(GattJob::CharRead)) {
// characteristic reading ***************************************
if (!service->characteristicList.contains(nextJob.handle)) {
qCWarning(QT_BT_BLUEZ) << "Invalid Char handle when reading. Skipping.";
prepareNextJob();
return;
}
const QLowEnergyServicePrivate::CharData &charData =
service->characteristicList.value(nextJob.handle);
bool foundChar = false;
for (const auto &gattChar : std::as_const(dbusServiceData.characteristics)) {
if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID()))
continue;
QDBusPendingReply<QByteArray> reply = gattChar.characteristic->ReadValue(QVariantMap());
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
this, &QLowEnergyControllerPrivateBluezDBus::onCharReadFinished);
foundChar = true;
break;
}
if (!foundChar) {
qCWarning(QT_BT_BLUEZ) << "Cannot find char for reading. Skipping.";
prepareNextJob();
return;
}
} else if (nextJob.flags.testFlag(GattJob::CharWrite)) {
// characteristic writing ***************************************
if (!service->characteristicList.contains(nextJob.handle)) {
qCWarning(QT_BT_BLUEZ) << "Invalid Char handle when writing. Skipping.";
prepareNextJob();
return;
}
const QLowEnergyServicePrivate::CharData &charData =
service->characteristicList.value(nextJob.handle);
bool foundChar = false;
for (const auto &gattChar : std::as_const(dbusServiceData.characteristics)) {
if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID()))
continue;
QVariantMap options;
// The "type" option only works with BlueZ >= 5.50, older versions always write with response
options[QStringLiteral("type")] = nextJob.writeMode == QLowEnergyService::WriteWithoutResponse ?
QStringLiteral("command") : QStringLiteral("request");
QDBusPendingReply<> reply = gattChar.characteristic->WriteValue(nextJob.value, options);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
this, &QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished);
foundChar = true;
break;
}
if (!foundChar) {
qCWarning(QT_BT_BLUEZ) << "Cannot find char for writing. Skipping.";
prepareNextJob();
return;
}
} else if (nextJob.flags.testFlag(GattJob::DescRead)) {
// descriptor reading ***************************************
QLowEnergyCharacteristic ch = characteristicForHandle(nextJob.handle);
if (!ch.isValid()) {
qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleReadDesc 1). Skipping.";
prepareNextJob();
return;
}
const QLowEnergyServicePrivate::CharData &charData =
service->characteristicList.value(ch.attributeHandle());
if (!charData.descriptorList.contains(nextJob.handle)) {
qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleReadDesc 2). Skipping.";
prepareNextJob();
return;
}
const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid;
bool foundDesc = false;
for (const auto &gattChar : std::as_const(dbusServiceData.characteristics)) {
if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID()))
continue;
for (const auto &gattDesc : std::as_const(gattChar.descriptors)) {
if (descUuid != QBluetoothUuid(gattDesc->uUID()))
continue;
QDBusPendingReply<QByteArray> reply = gattDesc->ReadValue(QVariantMap());
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
this, &QLowEnergyControllerPrivateBluezDBus::onDescReadFinished);
foundDesc = true;
break;
}
if (foundDesc)
break;
}
if (!foundDesc) {
qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor for reading. Skipping.";
prepareNextJob();
return;
}
} else if (nextJob.flags.testFlag(GattJob::DescWrite)) {
// descriptor writing ***************************************
const QLowEnergyCharacteristic ch = characteristicForHandle(nextJob.handle);
if (!ch.isValid()) {
qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleWriteDesc 1). Skipping.";
prepareNextJob();
return;
}
const QLowEnergyServicePrivate::CharData &charData =
service->characteristicList.value(ch.attributeHandle());
if (!charData.descriptorList.contains(nextJob.handle)) {
qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleWriteDesc 2). Skipping.";
prepareNextJob();
return;
}
const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid;
bool foundDesc = false;
for (const auto &gattChar : std::as_const(dbusServiceData.characteristics)) {
if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID()))
continue;
for (const auto &gattDesc : std::as_const(gattChar.descriptors)) {
if (descUuid != QBluetoothUuid(gattDesc->uUID()))
continue;
//notifications enabled via characteristics Start/StopNotify() functions
//otherwise regular WriteValue() calls on descriptor interface
if (descUuid == QBluetoothUuid(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration)) {
const QByteArray value = nextJob.value;
QDBusPendingReply<> reply;
qCDebug(QT_BT_BLUEZ) << "Init CCC change to" << value.toHex()
<< charData.uuid << service->uuid;
if (value == QByteArray::fromHex("0100") || value == QByteArray::fromHex("0200"))
reply = gattChar.characteristic->StartNotify();
else
reply = gattChar.characteristic->StopNotify();
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
this, &QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished);
} else {
QDBusPendingReply<> reply = gattDesc->WriteValue(nextJob.value, QVariantMap());
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
this, &QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished);
}
foundDesc = true;
break;
}
if (foundDesc)
break;
}
if (!foundDesc) {
qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor for writing. Skipping.";
prepareNextJob();
return;
}
} else {
qCWarning(QT_BT_BLUEZ) << "Unknown gatt job type. Skipping.";
prepareNextJob();
}
}
void QLowEnergyControllerPrivateBluezDBus::readCharacteristic(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle)
{
Q_ASSERT(!service.isNull());
if (!service->characteristicList.contains(charHandle)) {
qCWarning(QT_BT_BLUEZ) << "Read characteristic does not belong to service"
<< service->uuid;
return;
}
const QLowEnergyServicePrivate::CharData &charDetails
= service->characteristicList[charHandle];
if (!(charDetails.properties & QLowEnergyCharacteristic::Read)) {
// if this succeeds the device has a bug, char is advertised as
// non-readable. We try to be permissive and let the remote
// device answer to the read attempt
qCWarning(QT_BT_BLUEZ) << "Reading non-readable char" << charHandle;
}
const GattService &gattService = dbusServices[service->uuid];
if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) {
// Reread from dbus interface and write to local cache
const QByteArray newValue(1, char(gattService.batteryInterface->percentage()));
quint16 result = updateValueOfCharacteristic(charHandle, newValue, false);
if (result > 0) {
QLowEnergyCharacteristic ch(service, charHandle);
emit service->characteristicRead(ch, newValue);
} else {
service->setError(QLowEnergyService::CharacteristicReadError);
}
return;
}
GattJob job;
job.flags = GattJob::JobFlags({GattJob::CharRead});
job.service = service;
job.handle = charHandle;
jobs.append(job);
scheduleNextJob();
}
void QLowEnergyControllerPrivateBluezDBus::readDescriptor(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
const QLowEnergyHandle descriptorHandle)
{
Q_ASSERT(!service.isNull());
if (!service->characteristicList.contains(charHandle))
return;
const QLowEnergyServicePrivate::CharData &charDetails
= service->characteristicList[charHandle];
if (!charDetails.descriptorList.contains(descriptorHandle))
return;
const GattService &gattService = dbusServices[service->uuid];
if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) {
auto descriptor = descriptorForHandle(descriptorHandle);
if (descriptor.isValid())
emit service->descriptorRead(descriptor, descriptor.value());
else
service->setError(QLowEnergyService::DescriptorReadError);
return;
}
GattJob job;
job.flags = GattJob::JobFlags({GattJob::DescRead});
job.service = service;
job.handle = descriptorHandle;
jobs.append(job);
scheduleNextJob();
}
void QLowEnergyControllerPrivateBluezDBus::writeCharacteristic(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
const QByteArray &newValue,
QLowEnergyService::WriteMode writeMode)
{
Q_ASSERT(!service.isNull());
if (!service->characteristicList.contains(charHandle)) {
qCWarning(QT_BT_BLUEZ) << "Write characteristic does not belong to service"
<< service->uuid;
return;
}
if (role == QLowEnergyController::CentralRole) {
const GattService &gattService = dbusServices[service->uuid];
if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) {
//Battery1 interface is readonly
service->setError(QLowEnergyService::CharacteristicWriteError);
return;
}
GattJob job;
job.flags = GattJob::JobFlags({GattJob::CharWrite});
job.service = service;
job.handle = charHandle;
job.value = newValue;
job.writeMode = writeMode;
jobs.append(job);
scheduleNextJob();
} else {
// 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;
}
}
void QLowEnergyControllerPrivateBluezDBus::writeDescriptor(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
const QLowEnergyHandle descriptorHandle,
const QByteArray &newValue)
{
Q_ASSERT(!service.isNull());
if (!service->characteristicList.contains(charHandle))
return;
if (role == QLowEnergyController::CentralRole) {
const GattService &gattService = dbusServices[service->uuid];
if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) {
auto descriptor = descriptorForHandle(descriptorHandle);
if (!descriptor.isValid())
return;
if (descriptor.uuid() == QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration) {
if (newValue == QByteArray::fromHex("0000")
|| newValue == QByteArray::fromHex("0100")
|| newValue == QByteArray::fromHex("0200")) {
quint16 result = updateValueOfDescriptor(charHandle, descriptorHandle, newValue, false);
if (result > 0)
emit service->descriptorWritten(descriptor, newValue);
else
emit service->setError(QLowEnergyService::DescriptorWriteError);
}
} else {
service->setError(QLowEnergyService::DescriptorWriteError);
}
return;
}
GattJob job;
job.flags = GattJob::JobFlags({GattJob::DescWrite});
job.service = service;
job.handle = descriptorHandle;
job.value = newValue;
jobs.append(job);
scheduleNextJob();
} else {
// Peripheral role
Q_ASSERT(peripheralApplication);
auto desc = descriptorForHandle(descriptorHandle);
if (desc.uuid() == QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration) {
qCWarning(QT_BT_BLUEZ) << "CCCD write not supported in peripheral role";
service->setError(QLowEnergyService::DescriptorWriteError);
return;
} else if (!peripheralApplication->localDescriptorWrite(descriptorHandle, newValue)) {
qCWarning(QT_BT_BLUEZ) << "Descriptor write failed" << desc.uuid();
service->setError(QLowEnergyService::DescriptorWriteError);
return;
}
service->characteristicList[charHandle].descriptorList[descriptorHandle].value = newValue;
}
}
void QLowEnergyControllerPrivateBluezDBus::startAdvertising(
const QLowEnergyAdvertisingParameters ¶ms,
const QLowEnergyAdvertisingData &advertisingData,
const QLowEnergyAdvertisingData &scanResponseData)
{
error = QLowEnergyController::NoError;
errorString.clear();
Q_ASSERT(peripheralApplication);
Q_ASSERT(!adapterPathWithPeripheralSupport.isEmpty());
if (advertiser) {
// Clear any previous advertiser in case advertising data has changed.
// For clarity: this function is called only in 'Unconnected' state
delete advertiser;
advertiser = nullptr;
}
advertiser = new QLeDBusAdvertiser(params, advertisingData, scanResponseData,
adapterPathWithPeripheralSupport, this);
connect(advertiser, &QLeDBusAdvertiser::errorOccurred,
this, &QLowEnergyControllerPrivateBluezDBus::handleAdvertisingError);
setState(QLowEnergyController::AdvertisingState);
// First register the application to bluez if needed, and then start the advertisement.
// The application registration may fail and is asynchronous => serialize the steps.
// For clarity: advertisements can be used without any services, but registering such
// application to Bluez would fail
if (peripheralApplication->registrationNeeded())
peripheralApplication->registerApplication();
else
advertiser->startAdvertising();
}
void QLowEnergyControllerPrivateBluezDBus::stopAdvertising()
{
// This function is called only in Advertising state
setState(QLowEnergyController::UnconnectedState);
if (advertiser) {
advertiser->stopAdvertising();
delete advertiser;
advertiser = nullptr;
}
}
void QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationRegistered()
{
// Start the actual advertising now that the application is registered.
// Check the state first in case user has called stopAdvertising() during
// application registration
if (advertiser && state == QLowEnergyController::AdvertisingState)
advertiser->startAdvertising();
else
peripheralApplication->unregisterApplication();
}
void QLowEnergyControllerPrivateBluezDBus::handlePeripheralCharacteristicValueUpdate(
QLowEnergyHandle handle, const QByteArray& value)
{
const auto characteristic = characteristicForHandle(handle);
if (characteristic.d_ptr
&& updateValueOfCharacteristic(handle, value, false) == value.size()) {
emit characteristic.d_ptr->characteristicChanged(characteristic, value);
} else {
qCWarning(QT_BT_BLUEZ) << "Remote characteristic write failed";
}
}
void QLowEnergyControllerPrivateBluezDBus::handlePeripheralDescriptorValueUpdate(
QLowEnergyHandle characteristicHandle,
QLowEnergyHandle descriptorHandle,
const QByteArray& value)
{
const auto descriptor = descriptorForHandle(descriptorHandle);
if (descriptor.d_ptr && updateValueOfDescriptor(
characteristicHandle, descriptorHandle, value, false) == value.size()) {
emit descriptor.d_ptr->descriptorWritten(descriptor, value);
} else {
qCWarning(QT_BT_BLUEZ) << "Remote descriptor write failed";
}
}
void QLowEnergyControllerPrivateBluezDBus::handlePeripheralRemoteDeviceChanged(
const QBluetoothAddress& address,
const QString& name,
quint16 mtu)
{
remoteDevice = address;
remoteName = name;
remoteMtu = mtu;
}
void QLowEnergyControllerPrivateBluezDBus::handleAdvertisingError()
{
Q_ASSERT(peripheralApplication);
qCWarning(QT_BT_BLUEZ) << "An advertising error occurred";
setError(QLowEnergyController::AdvertisingError);
setState(QLowEnergyController::UnconnectedState);
peripheralApplication->unregisterApplication();
}
void QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationError()
{
qCWarning(QT_BT_BLUEZ) << "A Bluez peripheral application error occurred";
setError(QLowEnergyController::UnknownError);
setState(QLowEnergyController::UnconnectedState);
}
void QLowEnergyControllerPrivateBluezDBus::handlePeripheralConnectivityChanged(bool connected)
{
Q_Q(QLowEnergyController);
qCDebug(QT_BT_BLUEZ) << "Peripheral application connected change to:" << connected;
if (connected) {
setState(QLowEnergyController::ConnectedState);
} else {
resetController();
remoteDevice.clear();
setState(QLowEnergyController::UnconnectedState);
emit q->disconnected();
}
}
void QLowEnergyControllerPrivateBluezDBus::requestConnectionUpdate(
const QLowEnergyConnectionParameters & /* params */)
{
qCWarning(QT_BT_BLUEZ) << "Connection update requests not supported on Bluez DBus";
}
void QLowEnergyControllerPrivateBluezDBus::addToGenericAttributeList(
const QLowEnergyServiceData &serviceData,
QLowEnergyHandle startHandle)
{
Q_ASSERT(peripheralApplication);
QSharedPointer<QLowEnergyServicePrivate> servicePrivate = serviceForHandle(startHandle);
if (servicePrivate.isNull())
return;
peripheralApplication->addService(serviceData, servicePrivate, startHandle);
}
int QLowEnergyControllerPrivateBluezDBus::mtu() const
{
// currently only supported on peripheral role
return remoteMtu;
}
QT_END_NAMESPACE
#include "moc_qlowenergycontroller_bluezdbus_p.cpp"