// 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) 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 newUuidList; for (const QString &uuidString : newUuidStringList) newUuidList.append(QBluetoothUuid(uuidString)); for (const QBluetoothUuid &uuid : serviceList.keys()) { if (!newUuidList.contains(uuid)) { qCDebug(QT_BT_BLUEZ) << __func__ << "Service" << uuid << "has been removed"; QSharedPointer 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 serviceData = serviceList.value(uuid); if (serviceData->state != QLowEnergyService::RemoteServiceDiscovered) return; QHash::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( QStringLiteral("org.bluez"), QStringLiteral("/"), QDBusConnection::systemBus()); QDBusPendingReply 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(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 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 priv = QSharedPointer::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")) { for (auto const& uuid : battIter.value()[QStringLiteral("UUIDs")].toStringList()) { 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 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 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::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 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 reply = managerBluez->GetManagedObjects(); reply.waitForFinished(); if (reply.isError()) { qCWarning(QT_BT_BLUEZ) << "Cannot discover services"; setError(QLowEnergyController::UnknownError); setState(QLowEnergyController::DiscoveredState); return; } QStringList descriptorPaths; 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::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::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::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 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 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 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 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 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 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 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 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 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 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 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 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 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 udpate requests not supported on Bluez DBus"; } void QLowEnergyControllerPrivateBluezDBus::addToGenericAttributeList( const QLowEnergyServiceData &serviceData, QLowEnergyHandle startHandle) { Q_ASSERT(peripheralApplication); QSharedPointer 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"