Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qlowenergycontroller_bluezdbus.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
9#include "bluez/gattchar1_p.h"
10#include "bluez/gattdesc1_p.h"
11#include "bluez/battery1_p.h"
13#include "bluez/properties_p.h"
16
18
20
22 const QString &adapterPathWithPeripheralSupport)
24 adapterPathWithPeripheralSupport(adapterPathWithPeripheralSupport)
25{
26}
27
29{
31 qCWarning(QT_BT_BLUEZ) << "Low Energy Controller is not Unconnected when deleted."
32 << "Deleted in state:" << state;
33 }
34}
35
37{
39 Q_ASSERT(!adapterPathWithPeripheralSupport.isEmpty());
40
41 peripheralApplication = new QtBluezPeripheralApplication(adapterPathWithPeripheralSupport,
42 this);
43
45 &QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationError);
46
48 &QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationRegistered);
49
50 QObject::connect(peripheralApplication,
52 &QLowEnergyControllerPrivateBluezDBus::handlePeripheralCharacteristicValueUpdate);
53
54 QObject::connect(peripheralApplication,
56 &QLowEnergyControllerPrivateBluezDBus::handlePeripheralDescriptorValueUpdate);
57
58 peripheralConnectionManager =
60
61 QObject::connect(peripheralApplication,
63 peripheralConnectionManager,
65
66 QObject::connect(peripheralConnectionManager,
68 &QLowEnergyControllerPrivateBluezDBus::handlePeripheralConnectivityChanged);
69
70 QObject::connect(peripheralConnectionManager,
72 &QLowEnergyControllerPrivateBluezDBus::handlePeripheralRemoteDeviceChanged);
73 }
74}
75
76void QLowEnergyControllerPrivateBluezDBus::devicePropertiesChanged(
77 const QString &interface, const QVariantMap &changedProperties,
78 const QStringList &/*removedProperties*/)
79{
80 if (interface == QStringLiteral("org.bluez.Device1")) {
81 qCDebug(QT_BT_BLUEZ) << "######" << interface << changedProperties;
82 if (changedProperties.contains(QStringLiteral("ServicesResolved"))) {
83 //we could check for Connected property as well, but we choose to wait
84 //for ServicesResolved being true
85
86 if (pendingConnect) {
87 bool isResolved = changedProperties.value(QStringLiteral("ServicesResolved")).toBool();
88 if (isResolved) {
90 pendingConnect = false;
91 disconnectSignalRequired = true;
93 emit q->connected();
94 }
95 }
96 }
97
98 if (changedProperties.contains(QStringLiteral("Connected"))) {
99 bool isConnected = changedProperties.value(QStringLiteral("Connected")).toBool();
100 if (!isConnected) {
101 switch (state) {
107 {
109 if (pendingConnect)
111
112 executeClose(newError);
113 }
114 break;
117 //ignore
118 break;
119 }
120 }
121 }
122
123 if (changedProperties.contains(QStringLiteral("UUIDs"))) {
124 const QStringList newUuidStringList = changedProperties.value(QStringLiteral("UUIDs")).toStringList();
125 QList<QBluetoothUuid> newUuidList;
126 for (const QString &uuidString : newUuidStringList)
127 newUuidList.append(QBluetoothUuid(uuidString));
128
129 for (const QBluetoothUuid &uuid : serviceList.keys()) {
130 if (!newUuidList.contains(uuid)) {
131 qCDebug(QT_BT_BLUEZ) << __func__ << "Service" << uuid << "has been removed";
132 QSharedPointer<QLowEnergyServicePrivate> service = serviceList.take(uuid);
133 service->setController(nullptr);
134 dbusServices.remove(uuid);
135 }
136 }
137 }
138
139 } else if (interface == QStringLiteral("org.bluez.Battery1")) {
140 qCDebug(QT_BT_BLUEZ) << "######" << interface << changedProperties;
141 if (changedProperties.contains(QStringLiteral("Percentage"))) {
142 // if battery service is discovered and ClientCharConfig is enabled
143 // emit characteristicChanged() signal
145 if (!serviceList.contains(uuid) || !dbusServices.contains(uuid)
146 || !dbusServices[uuid].hasBatteryService
147 || dbusServices[uuid].batteryInterface.isNull())
148 return;
149
150 QSharedPointer<QLowEnergyServicePrivate> serviceData = serviceList.value(uuid);
151 if (serviceData->state != QLowEnergyService::RemoteServiceDiscovered)
152 return;
153
155 iter = serviceData->characteristicList.begin();
156 while (iter != serviceData->characteristicList.end()) {
157 auto &charData = iter.value();
159 continue;
160
161 // Client Characteristic Notification enabled?
162 bool cccActive = false;
163 for (const QLowEnergyServicePrivate::DescData &descData : std::as_const(charData.descriptorList)) {
165 continue;
166 if (descData.value == QByteArray::fromHex("0100")
167 || descData.value == QByteArray::fromHex("0200")) {
168 cccActive = true;
169 break;
170 }
171 }
172
173 const QByteArray newValue(1, char(dbusServices[uuid].batteryInterface->percentage()));
174 qCDebug(QT_BT_BLUEZ) << "Battery1 char update" << cccActive
175 << charData.value.toHex() << "->" << newValue.toHex();
176 if (cccActive && newValue != charData.value) {
177 qCDebug(QT_BT_BLUEZ) << "Property update for Battery1";
178 charData.value = newValue;
179 QLowEnergyCharacteristic ch(serviceData, iter.key());
180 emit serviceData->characteristicChanged(ch, newValue);
181 }
182
183 break;
184 }
185 }
186 }
187}
188
189void QLowEnergyControllerPrivateBluezDBus::characteristicPropertiesChanged(
190 QLowEnergyHandle charHandle, const QString &interface,
191 const QVariantMap &changedProperties,
192 const QStringList &/*removedProperties*/)
193{
194 //qCDebug(QT_BT_BLUEZ) << "$$$$$$$$$$$$$$$$$$ char monitor"
195 // << interface << changedProperties << charHandle;
196 if (interface != QStringLiteral("org.bluez.GattCharacteristic1"))
197 return;
198
199 if (!changedProperties.contains(QStringLiteral("Value")))
200 return;
201
202 const QLowEnergyCharacteristic changedChar = characteristicForHandle(charHandle);
203 const QLowEnergyDescriptor ccnDescriptor = changedChar.descriptor(
205 if (!ccnDescriptor.isValid())
206 return;
207
208 const QByteArray newValue = changedProperties.value(QStringLiteral("Value")).toByteArray();
209 if (changedChar.properties() & QLowEnergyCharacteristic::Read)
210 updateValueOfCharacteristic(charHandle, newValue, false); //TODO upgrade to NEW_VALUE/APPEND_VALUE
211
212 auto service = serviceForHandle(charHandle);
213
214 if (!service.isNull())
215 emit service->characteristicChanged(changedChar, newValue);
216}
217
218void QLowEnergyControllerPrivateBluezDBus::interfacesRemoved(const QDBusObjectPath &objectPath,
219 const QStringList &interfaces)
220{
221 if (objectPath.path() == device->path()) {
222 if (interfaces.contains(QStringLiteral("org.bluez.Device1"))) {
223 qCWarning(QT_BT_BLUEZ) << "DBus Device1 was removed";
225 } else {
226 qCDebug(QT_BT_BLUEZ) << "DBus interfaces" << interfaces << "were removed from"
227 << objectPath.path();
228 }
229 } else if (objectPath.path() == adapter->path()) {
230 qCWarning(QT_BT_BLUEZ) << "DBus Adapter was removed";
232 }
233}
234
235void QLowEnergyControllerPrivateBluezDBus::resetController()
236{
237 if (managerBluez) {
238 delete managerBluez;
239 managerBluez = nullptr;
240 }
241
242 if (adapter) {
243 delete adapter;
244 adapter = nullptr;
245 }
246
247 if (device) {
248 delete device;
249 device = nullptr;
250 }
251
252 if (deviceMonitor) {
253 delete deviceMonitor;
254 deviceMonitor = nullptr;
255 }
256
257 if (advertiser) {
258 delete advertiser;
259 advertiser = nullptr;
260 }
261
262 if (peripheralApplication)
263 peripheralApplication->reset();
264
265 if (peripheralConnectionManager)
266 peripheralConnectionManager->reset();
267
269 remoteMtu = -1;
270
271 dbusServices.clear();
272 jobs.clear();
274
275 pendingConnect = disconnectSignalRequired = false;
276 jobPending = false;
277}
278
279void QLowEnergyControllerPrivateBluezDBus::connectToDeviceHelper()
280{
281 resetController();
282
283 bool ok = false;
284 const QString hostAdapterPath = findAdapterForAddress(localAdapter, &ok);
285 if (!ok || hostAdapterPath.isEmpty()) {
286 qCWarning(QT_BT_BLUEZ) << "Cannot find suitable bluetooth adapter";
288 return;
289 }
290
291 auto manager = std::make_unique<OrgFreedesktopDBusObjectManagerInterface>(
293
294 QDBusPendingReply<ManagedObjectList> reply = manager->GetManagedObjects();
295 reply.waitForFinished();
296 if (reply.isError()) {
297 qCWarning(QT_BT_BLUEZ) << "Cannot enumerate Bluetooth devices for GATT connect";
299 return;
300 }
301
302 QString devicePath;
303 ManagedObjectList managedObjectList = reply.value();
304 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
305 const InterfaceList &ifaceList = it.value();
306
307 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
308 const QString &iface = jt.key();
309 const QVariantMap &ifaceValues = jt.value();
310
311 if (iface == QStringLiteral("org.bluez.Device1")) {
312 if (remoteDevice.toString() == ifaceValues.value(QStringLiteral("Address")).toString())
313 {
314 const QVariant adapterForCurrentDevice = ifaceValues.value(QStringLiteral("Adapter"));
315 if (qvariant_cast<QDBusObjectPath>(adapterForCurrentDevice).path() == hostAdapterPath) {
316 devicePath = it.key().path();
317 break;
318 }
319 }
320 }
321 }
322
323 if (!devicePath.isEmpty())
324 break;
325 }
326
327 if (devicePath.isEmpty()) {
328 qCDebug(QT_BT_BLUEZ) << "Cannot find targeted remote device. "
329 "Re-running device discovery might help";
331 return;
332 }
333
334 managerBluez = manager.release();
336 this, &QLowEnergyControllerPrivateBluezDBus::interfacesRemoved);
337 adapter = new OrgBluezAdapter1Interface(
338 QStringLiteral("org.bluez"), hostAdapterPath,
341 QStringLiteral("org.bluez"), devicePath,
343 deviceMonitor = new OrgFreedesktopDBusPropertiesInterface(
344 QStringLiteral("org.bluez"), devicePath,
347 this, &QLowEnergyControllerPrivateBluezDBus::devicePropertiesChanged);
348}
349
351{
352 qCDebug(QT_BT_BLUEZ) << "QLowEnergyControllerPrivateBluezDBus::connectToDevice()";
353
354 connectToDeviceHelper();
355
356 if (!adapter || !device)
357 return;
358
359 if (!adapter->powered()) {
360 qCWarning(QT_BT_BLUEZ) << "Error: Local adapter is powered off";
362 return;
363 }
364
366
367 //Bluez interface is shared among all platform processes
368 //and hence we might be connected already
369 if (device->connected() && device->servicesResolved()) {
370 //connectToDevice is noop
371 disconnectSignalRequired = true;
372
375 emit q->connected();
376 return;
377 }
378
379 pendingConnect = true;
380
381 QDBusPendingReply<> reply = device->Connect();
384 [this](QDBusPendingCallWatcher* call) {
386 if (reply.isError()) {
387 qCDebug(QT_BT_BLUEZ) << "BTLE_DBUS::connect() failed"
388 << reply.reply().errorName()
389 << reply.reply().errorMessage();
390 executeClose(QLowEnergyController::UnknownError);
391 } // else -> connected when Connected property is set to true (see devicePropertiesChanged())
392 call->deleteLater();
393 });
394}
395
397{
399 if (!device)
400 return;
401
403
404 QDBusPendingReply<> reply = device->Disconnect();
407 [this](QDBusPendingCallWatcher* call) {
409 if (reply.isError()) {
410 qCDebug(QT_BT_BLUEZ) << "BTLE_DBUS::disconnect() failed"
411 << reply.reply().errorName()
412 << reply.reply().errorMessage();
413 executeClose(QLowEnergyController::UnknownError);
414 } else {
415 executeClose(QLowEnergyController::NoError);
416 }
417 call->deleteLater();
418 });
419 } else {
421 peripheralConnectionManager->disconnectDevices();
422 resetController();
423 remoteDevice.clear();
424 const auto emitDisconnected = (state == QLowEnergyController::ConnectedState);
426 if (emitDisconnected)
427 emit q->disconnected();
428 }
429}
430
432{
433 QDBusPendingReply<ManagedObjectList> reply = managerBluez->GetManagedObjects();
434 reply.waitForFinished();
435 if (reply.isError()) {
436 qCWarning(QT_BT_BLUEZ) << "Cannot discover services";
439 return;
440 }
441
443
444 auto setupServicePrivate = [&, q](
446 const QString &path, const bool battery1Interface = false){
447 QSharedPointer<QLowEnergyServicePrivate> priv = QSharedPointer<QLowEnergyServicePrivate>::create();
448 priv->uuid = uuid;
449 priv->type = type; // we make a guess we cannot validate
450 priv->setController(this);
451
452 GattService serviceContainer;
453 serviceContainer.servicePath = path;
454
455 if (battery1Interface) {
456 qCDebug(QT_BT_BLUEZ) << "Using Battery1 interface to emulate generic interface";
457 serviceContainer.hasBatteryService = true;
458 }
459
460 serviceList.insert(priv->uuid, priv);
461 dbusServices.insert(priv->uuid, serviceContainer);
462
463 emit q->serviceDiscovered(priv->uuid);
464 };
465
466 const ManagedObjectList managedObjectList = reply.value();
467 const QString servicePathPrefix = device->path().append(QStringLiteral("/service"));
468
469 // The Bluez battery service (0x180f) support has evolved over time and needs additional logic:
470 //
471 // * Until 5.47 Bluez exposes battery services via generic interface (GattService1) only
472 // * Between 5.48..5.54 Bluez exposes battery service as a dedicated 'Battery1' interface only
473 // * From 5.55 Bluez exposes both the generic service as well as the dedicated 'Battery1'
474 //
475 // To hide the difference from users the 'Battery1' interface will be used to emulate the
476 // generic interface. Importantly also the GattService1 interface, if available, is available
477 // early whereas the Battery1 interface may be available only later (generated too late for
478 // this service discovery's purposes)
479 //
480 // The precedence for battery service here is as follows:
481 // * If available via GattService1, use that and ignore possible Battery1 interface
482 // * If Battery1 interface is available or the 'org.bluez.Device1' lists battery service
483 // amongst list of available services, mark the service such that the code will later
484 // look up the Battery1 service details
485 bool gattBatteryService{false};
486 QString batteryServicePath;
487
488 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
489 const InterfaceList &ifaceList = it.value();
490
491 if (!it.key().path().startsWith(device->path()))
492 continue;
493
494 if (it.key().path() == device->path()) {
495 // See if the battery service is available or is assumed to be available later
496 for (InterfaceList::const_iterator battIter = ifaceList.constBegin(); battIter != ifaceList.constEnd(); ++battIter) {
497 const QString &iface = battIter.key();
498 if (iface == QStringLiteral("org.bluez.Battery1")) {
499 qCDebug(QT_BT_BLUEZ) << "Dedicated Battery1 service available";
500 batteryServicePath = it.key().path();
501 break;
502 } else if (iface == QStringLiteral("org.bluez.Device1")) {
503 for (auto const& uuid :
504 battIter.value()[QStringLiteral("UUIDs")].toStringList()) {
505 if (QBluetoothUuid(uuid) ==
507 qCDebug(QT_BT_BLUEZ) << "Battery service listed as available service";
508 batteryServicePath = it.key().path();
509 break;
510 }
511 }
512 }
513 }
514 continue;
515 }
516
517 if (!it.key().path().startsWith(servicePathPrefix))
518 continue;
519
520 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
521 const QString &iface = jt.key();
522
523 if (iface == QStringLiteral("org.bluez.GattService1")) {
524 QScopedPointer<OrgBluezGattService1Interface> service(new OrgBluezGattService1Interface(
525 QStringLiteral("org.bluez"),it.key().path(),
527 if (QBluetoothUuid(service->uUID()) ==
529 qCDebug(QT_BT_BLUEZ) << "Using battery service via GattService1 interface";
530 gattBatteryService = true;
531 }
532 setupServicePrivate(service->primary()
535 QBluetoothUuid(service->uUID()), it.key().path());
536 }
537 }
538 }
539
540 if (!gattBatteryService && !batteryServicePath.isEmpty()) {
541 setupServicePrivate(QLowEnergyService::PrimaryService,
543 batteryServicePath, true);
544 }
545
547 emit q->discoveryFinished();
548}
549
550void QLowEnergyControllerPrivateBluezDBus::discoverBatteryServiceDetails(
551 GattService &dbusData, QSharedPointer<QLowEnergyServicePrivate> serviceData)
552{
553 // This process exists to work around the fact that Battery services (0x180f)
554 // are not mapped as generic services but use the Battery1 interface.
555 // Artificial chararacteristics and descriptors are created to emulate the generic behavior.
556
558 QStringLiteral("org.bluez"), dbusData.servicePath,
560 dbusData.batteryInterface = batteryService;
561
562 serviceData->startHandle = runningHandle++; //service start handle
563
564 // Create BatteryLevel char
565 QLowEnergyHandle indexHandle = runningHandle++; // char handle index
567
568 charData.valueHandle = runningHandle++;
569 charData.properties.setFlag(QLowEnergyCharacteristic::Read);
570 charData.properties.setFlag(QLowEnergyCharacteristic::Notify);
572 charData.value = QByteArray(1, char(batteryService->percentage()));
573
574 // Create the descriptors for the BatteryLevel
575 // They are hardcoded although CCC may change
577 QLowEnergyHandle descriptorHandle = runningHandle++;
579 descData.value = QByteArray::fromHex("0000"); // all configs off
580 charData.descriptorList.insert(descriptorHandle, descData);
581
582 descriptorHandle = runningHandle++;
584 //for details see Characteristic Presentation Format Vol3, Part G 3.3.3.5
585 // unsigend 8 bit, exp=1, org.bluetooth.unit.percentage, namespace & description
586 // bit order: little endian
587 descData.value = QByteArray::fromHex("0400ad27011131");
588 charData.descriptorList.insert(descriptorHandle, descData);
589
590 descriptorHandle = runningHandle++;
592 descData.value = QByteArray::fromHex("0401");
593 charData.descriptorList.insert(descriptorHandle, descData);
594
595 serviceData->characteristicList[indexHandle] = charData;
596 serviceData->endHandle = runningHandle++;
597
598 serviceData->setState(QLowEnergyService::RemoteServiceDiscovered);
599}
600
601void QLowEnergyControllerPrivateBluezDBus::executeClose(QLowEnergyController::Error newError)
602{
603 const bool emitDisconnect = disconnectSignalRequired;
604
605 resetController();
606 if (newError != QLowEnergyController::NoError)
607 setError(newError);
608
610 if (emitDisconnect) {
612 emit q->disconnected();
613 }
614}
615
618{
619 if (!serviceList.contains(service) || !dbusServices.contains(service)) {
620 qCWarning(QT_BT_BLUEZ) << "Discovery of unknown service" << service.toString()
621 << "not possible";
622 return;
623 }
624
625 //clear existing service data and run new discovery
626 QSharedPointer<QLowEnergyServicePrivate> serviceData = serviceList.value(service);
627 serviceData->characteristicList.clear();
628
629 GattService &dbusData = dbusServices[service];
630 dbusData.characteristics.clear();
631
632 if (dbusData.hasBatteryService) {
633 qCDebug(QT_BT_BLUEZ) << "Triggering Battery1 service discovery on " << dbusData.servicePath;
634 discoverBatteryServiceDetails(dbusData, serviceData);
635 return;
636 }
637
638 QDBusPendingReply<ManagedObjectList> reply = managerBluez->GetManagedObjects();
639 reply.waitForFinished();
640 if (reply.isError()) {
641 qCWarning(QT_BT_BLUEZ) << "Cannot discover services";
644 return;
645 }
646
647 QStringList descriptorPaths;
648 const ManagedObjectList managedObjectList = reply.value();
649 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
650 const InterfaceList &ifaceList = it.value();
651 if (!it.key().path().startsWith(dbusData.servicePath))
652 continue;
653
654 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
655 const QString &iface = jt.key();
656 if (iface == QStringLiteral("org.bluez.GattCharacteristic1")) {
658 QStringLiteral("org.bluez"), it.key().path(),
660 GattCharacteristic dbusCharData;
661 dbusCharData.characteristic = charInterface;
662 dbusData.characteristics.append(dbusCharData);
663 } else if (iface == QStringLiteral("org.bluez.GattDescriptor1")) {
665 QStringLiteral("org.bluez"), it.key().path(),
667 bool found = false;
668 for (GattCharacteristic &dbusCharData : dbusData.characteristics) {
669 if (!descInterface->path().startsWith(
670 dbusCharData.characteristic->path()))
671 continue;
672
673 found = true;
674 dbusCharData.descriptors.append(descInterface);
675 break;
676 }
677
678 Q_ASSERT(found);
679 if (!found)
680 qCWarning(QT_BT_BLUEZ) << "Descriptor discovery error";
681 }
682 }
683 }
684
685 //populate servicePrivate based on dbus data
686 serviceData->startHandle = runningHandle++;
687 for (GattCharacteristic &dbusChar : dbusData.characteristics) {
688 const QLowEnergyHandle indexHandle = runningHandle++;
690
691 // characteristic data
692 charData.valueHandle = runningHandle++;
693 const QStringList properties = dbusChar.characteristic->flags();
694
695 for (const auto &entry : properties) {
696 if (entry == QStringLiteral("broadcast"))
697 charData.properties.setFlag(QLowEnergyCharacteristic::Broadcasting, true);
698 else if (entry == QStringLiteral("read"))
699 charData.properties.setFlag(QLowEnergyCharacteristic::Read, true);
700 else if (entry == QStringLiteral("write-without-response"))
701 charData.properties.setFlag(QLowEnergyCharacteristic::WriteNoResponse, true);
702 else if (entry == QStringLiteral("write"))
703 charData.properties.setFlag(QLowEnergyCharacteristic::Write, true);
704 else if (entry == QStringLiteral("notify"))
705 charData.properties.setFlag(QLowEnergyCharacteristic::Notify, true);
706 else if (entry == QStringLiteral("indicate"))
707 charData.properties.setFlag(QLowEnergyCharacteristic::Indicate, true);
708 else if (entry == QStringLiteral("authenticated-signed-writes"))
709 charData.properties.setFlag(QLowEnergyCharacteristic::WriteSigned, true);
710 else if (entry == QStringLiteral("reliable-write"))
711 charData.properties.setFlag(QLowEnergyCharacteristic::ExtendedProperty, true);
712 else if (entry == QStringLiteral("writable-auxiliaries"))
713 charData.properties.setFlag(QLowEnergyCharacteristic::ExtendedProperty, true);
714 //all others ignored - not relevant for this API
715 }
716
717 charData.uuid = QBluetoothUuid(dbusChar.characteristic->uUID());
718
719 // schedule read for initial char value
721 && charData.properties.testFlag(QLowEnergyCharacteristic::Read)) {
722 GattJob job;
723 job.flags = GattJob::JobFlags({GattJob::CharRead, GattJob::ServiceDiscovery});
724 job.service = serviceData;
725 job.handle = indexHandle;
726 jobs.append(job);
727 }
728
729 // descriptor data
730 for (const auto &descEntry : std::as_const(dbusChar.descriptors)) {
731 const QLowEnergyHandle descriptorHandle = runningHandle++;
733 descData.uuid = QBluetoothUuid(descEntry->uUID());
734 charData.descriptorList.insert(descriptorHandle, descData);
735
736
737 // every ClientCharacteristicConfiguration needs to track property changes
738 if (descData.uuid
741 QStringLiteral("org.bluez"),
742 dbusChar.characteristic->path(),
745 this, [this, indexHandle](const QString &interface, const QVariantMap &changedProperties,
746 const QStringList &removedProperties) {
747
748 characteristicPropertiesChanged(indexHandle, interface,
749 changedProperties, removedProperties);
750 });
751 }
752
754 // schedule read for initial descriptor value
755 GattJob job;
756 job.flags = GattJob::JobFlags({ GattJob::DescRead, GattJob::ServiceDiscovery });
757 job.service = serviceData;
758 job.handle = descriptorHandle;
759 jobs.append(job);
760 }
761 }
762
763 serviceData->characteristicList[indexHandle] = charData;
764 }
765
766 serviceData->endHandle = runningHandle++;
767
768 // last job is last step of service discovery
769 if (!jobs.isEmpty()) {
770 GattJob &lastJob = jobs.last();
771 lastJob.flags.setFlag(GattJob::LastServiceDiscovery, true);
772 } else {
773 serviceData->setState(QLowEnergyService::RemoteServiceDiscovered);
774 }
775
776 scheduleNextJob();
777}
778
779void QLowEnergyControllerPrivateBluezDBus::prepareNextJob()
780{
781 jobs.takeFirst(); // finish last job
782 jobPending = false;
783
784 scheduleNextJob(); // continue with next job - if available
785}
786
787void QLowEnergyControllerPrivateBluezDBus::onCharReadFinished(QDBusPendingCallWatcher *call)
788{
789 if (!jobPending || jobs.isEmpty()) {
790 // this may happen when service disconnects before dbus watcher returns later on
791 qCWarning(QT_BT_BLUEZ) << "Aborting onCharReadFinished due to disconnect";
793 return;
794 }
795
796 const GattJob nextJob = jobs.constFirst();
797 Q_ASSERT(nextJob.flags.testFlag(GattJob::CharRead));
798
799 QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(nextJob.handle);
800 if (service.isNull() || !dbusServices.contains(service->uuid)) {
801 qCWarning(QT_BT_BLUEZ) << "onCharReadFinished: Invalid GATT job. Skipping.";
802 call->deleteLater();
803 prepareNextJob();
804 return;
805 }
806 const QLowEnergyServicePrivate::CharData &charData =
807 service->characteristicList.value(nextJob.handle);
808
809 bool isServiceDiscovery = nextJob.flags.testFlag(GattJob::ServiceDiscovery);
810 QDBusPendingReply<QByteArray> reply = *call;
811 if (reply.isError()) {
812 qCWarning(QT_BT_BLUEZ) << "Cannot initiate reading of" << charData.uuid
813 << "of service" << service->uuid
814 << reply.error().name() << reply.error().message();
815 if (!isServiceDiscovery)
817 } else {
818 qCDebug(QT_BT_BLUEZ) << "Read Char:" << charData.uuid << reply.value().toHex();
819 if (charData.properties.testFlag(QLowEnergyCharacteristic::Read))
820 updateValueOfCharacteristic(nextJob.handle, reply.value(), false);
821
822 if (isServiceDiscovery) {
823 if (nextJob.flags.testFlag(GattJob::LastServiceDiscovery))
825 } else {
826 QLowEnergyCharacteristic ch(service, nextJob.handle);
827 emit service->characteristicRead(ch, reply.value());
828 }
829 }
830
831 call->deleteLater();
832 prepareNextJob();
833}
834
835void QLowEnergyControllerPrivateBluezDBus::onDescReadFinished(QDBusPendingCallWatcher *call)
836{
837 if (!jobPending || jobs.isEmpty()) {
838 // this may happen when service disconnects before dbus watcher returns later on
839 qCWarning(QT_BT_BLUEZ) << "Aborting onDescReadFinished due to disconnect";
841 return;
842 }
843
844 const GattJob nextJob = jobs.constFirst();
845 Q_ASSERT(nextJob.flags.testFlag(GattJob::DescRead));
846
847 QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(nextJob.handle);
848 if (service.isNull() || !dbusServices.contains(service->uuid)) {
849 qCWarning(QT_BT_BLUEZ) << "onDescReadFinished: Invalid GATT job. Skipping.";
850 call->deleteLater();
851 prepareNextJob();
852 return;
853 }
854
856 if (!ch.isValid()) {
857 qCWarning(QT_BT_BLUEZ) << "Cannot find char for desc read (onDescReadFinished 1).";
858 call->deleteLater();
859 prepareNextJob();
860 return;
861 }
862
863 const QLowEnergyServicePrivate::CharData &charData =
864 service->characteristicList.value(ch.attributeHandle());
865
866 if (!charData.descriptorList.contains(nextJob.handle)) {
867 qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor (onDescReadFinished 2).";
868 call->deleteLater();
869 prepareNextJob();
870 return;
871 }
872
873 bool isServiceDiscovery = nextJob.flags.testFlag(GattJob::ServiceDiscovery);
874
875 QDBusPendingReply<QByteArray> reply = *call;
876 if (reply.isError()) {
877 qCWarning(QT_BT_BLUEZ) << "Cannot read descriptor (onDescReadFinished 3): "
878 << charData.descriptorList[nextJob.handle].uuid
879 << charData.uuid
880 << reply.error().name() << reply.error().message();
881 if (!isServiceDiscovery)
883 } else {
884 qCDebug(QT_BT_BLUEZ) << "Read Desc:" << reply.value();
885 updateValueOfDescriptor(ch.attributeHandle(), nextJob.handle, reply.value(), false);
886
887 if (isServiceDiscovery) {
888 if (nextJob.flags.testFlag(GattJob::LastServiceDiscovery))
890 } else {
891 QLowEnergyDescriptor desc(service, ch.attributeHandle(), nextJob.handle);
892 emit service->descriptorRead(desc, reply.value());
893 }
894 }
895
896 call->deleteLater();
897 prepareNextJob();
898}
899
900void QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished(QDBusPendingCallWatcher *call)
901{
902 if (!jobPending || jobs.isEmpty()) {
903 // this may happen when service disconnects before dbus watcher returns later on
904 qCWarning(QT_BT_BLUEZ) << "Aborting onCharWriteFinished due to disconnect";
906 return;
907 }
908
909 const GattJob nextJob = jobs.constFirst();
910 Q_ASSERT(nextJob.flags.testFlag(GattJob::CharWrite));
911
912 QSharedPointer<QLowEnergyServicePrivate> service = nextJob.service;
913 if (!dbusServices.contains(service->uuid)) {
914 qCWarning(QT_BT_BLUEZ) << "onCharWriteFinished: Invalid GATT job. Skipping.";
915 call->deleteLater();
916 prepareNextJob();
917 return;
918 }
919
920 const QLowEnergyServicePrivate::CharData &charData =
921 service->characteristicList.value(nextJob.handle);
922
924 if (reply.isError()) {
925 qCWarning(QT_BT_BLUEZ) << "Cannot initiate writing of" << charData.uuid
926 << "of service" << service->uuid
927 << reply.error().name() << reply.error().message();
929 } else {
930 if (charData.properties.testFlag(QLowEnergyCharacteristic::Read))
931 updateValueOfCharacteristic(nextJob.handle, nextJob.value, false);
932
933 QLowEnergyCharacteristic ch(service, nextJob.handle);
934 // write without response implies zero feedback
935 if (nextJob.writeMode == QLowEnergyService::WriteWithResponse) {
936 qCDebug(QT_BT_BLUEZ) << "Written Char:" << charData.uuid << nextJob.value.toHex();
937 emit service->characteristicWritten(ch, nextJob.value);
938 }
939 }
940
941 call->deleteLater();
942 prepareNextJob();
943}
944
945void QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished(QDBusPendingCallWatcher *call)
946{
947 if (!jobPending || jobs.isEmpty()) {
948 // this may happen when service disconnects before dbus watcher returns later on
949 qCWarning(QT_BT_BLUEZ) << "Aborting onDescWriteFinished due to disconnect";
951 return;
952 }
953
954 const GattJob nextJob = jobs.constFirst();
955 Q_ASSERT(nextJob.flags.testFlag(GattJob::DescWrite));
956
957 QSharedPointer<QLowEnergyServicePrivate> service = nextJob.service;
958 if (!dbusServices.contains(service->uuid)) {
959 qCWarning(QT_BT_BLUEZ) << "onDescWriteFinished: Invalid GATT job. Skipping.";
960 call->deleteLater();
961 prepareNextJob();
962 return;
963 }
964
965 const QLowEnergyCharacteristic associatedChar = characteristicForHandle(nextJob.handle);
966 const QLowEnergyDescriptor descriptor = descriptorForHandle(nextJob.handle);
967 if (!associatedChar.isValid() || !descriptor.isValid()) {
968 qCWarning(QT_BT_BLUEZ) << "onDescWriteFinished: Cannot find associated char/desc: "
969 << associatedChar.isValid();
970 call->deleteLater();
971 prepareNextJob();
972 return;
973 }
974
976 if (reply.isError()) {
977 qCWarning(QT_BT_BLUEZ) << "Cannot initiate writing of" << descriptor.uuid()
978 << "of char" << associatedChar.uuid()
979 << "of service" << service->uuid
980 << reply.error().name() << reply.error().message();
982 } else {
983 qCDebug(QT_BT_BLUEZ) << "Write Desc:" << descriptor.uuid() << nextJob.value.toHex();
984 updateValueOfDescriptor(associatedChar.attributeHandle(), nextJob.handle,
985 nextJob.value, false);
986 emit service->descriptorWritten(descriptor, nextJob.value);
987 }
988
989 call->deleteLater();
990 prepareNextJob();
991}
992
993void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob()
994{
995 if (jobPending || jobs.isEmpty())
996 return;
997
998 jobPending = true;
999
1000 const GattJob nextJob = jobs.constFirst();
1001 QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(nextJob.handle);
1002 if (service.isNull() || !dbusServices.contains(service->uuid)) {
1003 qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleNextJob). Skipping.";
1004 prepareNextJob();
1005 return;
1006 }
1007
1008 const GattService &dbusServiceData = dbusServices[service->uuid];
1009
1010 if (nextJob.flags.testFlag(GattJob::CharRead)) {
1011 // characteristic reading ***************************************
1012 if (!service->characteristicList.contains(nextJob.handle)) {
1013 qCWarning(QT_BT_BLUEZ) << "Invalid Char handle when reading. Skipping.";
1014 prepareNextJob();
1015 return;
1016 }
1017
1018 const QLowEnergyServicePrivate::CharData &charData =
1019 service->characteristicList.value(nextJob.handle);
1020 bool foundChar = false;
1021 for (const auto &gattChar : std::as_const(dbusServiceData.characteristics)) {
1022 if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID()))
1023 continue;
1024
1025 QDBusPendingReply<QByteArray> reply = gattChar.characteristic->ReadValue(QVariantMap());
1028 this, &QLowEnergyControllerPrivateBluezDBus::onCharReadFinished);
1029
1030 foundChar = true;
1031 break;
1032 }
1033
1034 if (!foundChar) {
1035 qCWarning(QT_BT_BLUEZ) << "Cannot find char for reading. Skipping.";
1036 prepareNextJob();
1037 return;
1038 }
1039 } else if (nextJob.flags.testFlag(GattJob::CharWrite)) {
1040 // characteristic writing ***************************************
1041 if (!service->characteristicList.contains(nextJob.handle)) {
1042 qCWarning(QT_BT_BLUEZ) << "Invalid Char handle when writing. Skipping.";
1043 prepareNextJob();
1044 return;
1045 }
1046
1047 const QLowEnergyServicePrivate::CharData &charData =
1048 service->characteristicList.value(nextJob.handle);
1049 bool foundChar = false;
1050 for (const auto &gattChar : std::as_const(dbusServiceData.characteristics)) {
1051 if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID()))
1052 continue;
1053
1054 QVariantMap options;
1055 // The "type" option only works with BlueZ >= 5.50, older versions always write with response
1056 options[QStringLiteral("type")] = nextJob.writeMode == QLowEnergyService::WriteWithoutResponse ?
1057 QStringLiteral("command") : QStringLiteral("request");
1058 QDBusPendingReply<> reply = gattChar.characteristic->WriteValue(nextJob.value, options);
1059
1062 this, &QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished);
1063
1064 foundChar = true;
1065 break;
1066 }
1067
1068 if (!foundChar) {
1069 qCWarning(QT_BT_BLUEZ) << "Cannot find char for writing. Skipping.";
1070 prepareNextJob();
1071 return;
1072 }
1073 } else if (nextJob.flags.testFlag(GattJob::DescRead)) {
1074 // descriptor reading ***************************************
1076 if (!ch.isValid()) {
1077 qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleReadDesc 1). Skipping.";
1078 prepareNextJob();
1079 return;
1080 }
1081
1082 const QLowEnergyServicePrivate::CharData &charData =
1083 service->characteristicList.value(ch.attributeHandle());
1084 if (!charData.descriptorList.contains(nextJob.handle)) {
1085 qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleReadDesc 2). Skipping.";
1086 prepareNextJob();
1087 return;
1088 }
1089
1090 const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid;
1091 bool foundDesc = false;
1092 for (const auto &gattChar : std::as_const(dbusServiceData.characteristics)) {
1093 if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID()))
1094 continue;
1095
1096 for (const auto &gattDesc : std::as_const(gattChar.descriptors)) {
1097 if (descUuid != QBluetoothUuid(gattDesc->uUID()))
1098 continue;
1099
1100 QDBusPendingReply<QByteArray> reply = gattDesc->ReadValue(QVariantMap());
1103 this, &QLowEnergyControllerPrivateBluezDBus::onDescReadFinished);
1104 foundDesc = true;
1105 break;
1106 }
1107
1108 if (foundDesc)
1109 break;
1110 }
1111
1112 if (!foundDesc) {
1113 qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor for reading. Skipping.";
1114 prepareNextJob();
1115 return;
1116 }
1117 } else if (nextJob.flags.testFlag(GattJob::DescWrite)) {
1118 // descriptor writing ***************************************
1119 const QLowEnergyCharacteristic ch = characteristicForHandle(nextJob.handle);
1120 if (!ch.isValid()) {
1121 qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleWriteDesc 1). Skipping.";
1122 prepareNextJob();
1123 return;
1124 }
1125
1126 const QLowEnergyServicePrivate::CharData &charData =
1127 service->characteristicList.value(ch.attributeHandle());
1128 if (!charData.descriptorList.contains(nextJob.handle)) {
1129 qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleWriteDesc 2). Skipping.";
1130 prepareNextJob();
1131 return;
1132 }
1133
1134 const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid;
1135 bool foundDesc = false;
1136 for (const auto &gattChar : std::as_const(dbusServiceData.characteristics)) {
1137 if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID()))
1138 continue;
1139
1140 for (const auto &gattDesc : std::as_const(gattChar.descriptors)) {
1141 if (descUuid != QBluetoothUuid(gattDesc->uUID()))
1142 continue;
1143
1144 //notifications enabled via characteristics Start/StopNotify() functions
1145 //otherwise regular WriteValue() calls on descriptor interface
1147 const QByteArray value = nextJob.value;
1148
1150 qCDebug(QT_BT_BLUEZ) << "Init CCC change to" << value.toHex()
1151 << charData.uuid << service->uuid;
1152 if (value == QByteArray::fromHex("0100") || value == QByteArray::fromHex("0200"))
1153 reply = gattChar.characteristic->StartNotify();
1154 else
1155 reply = gattChar.characteristic->StopNotify();
1158 this, &QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished);
1159 } else {
1160 QDBusPendingReply<> reply = gattDesc->WriteValue(nextJob.value, QVariantMap());
1163 this, &QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished);
1164
1165 }
1166
1167 foundDesc = true;
1168 break;
1169 }
1170
1171 if (foundDesc)
1172 break;
1173 }
1174
1175 if (!foundDesc) {
1176 qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor for writing. Skipping.";
1177 prepareNextJob();
1178 return;
1179 }
1180 } else {
1181 qCWarning(QT_BT_BLUEZ) << "Unknown gatt job type. Skipping.";
1182 prepareNextJob();
1183 }
1184}
1185
1187 const QSharedPointer<QLowEnergyServicePrivate> service,
1188 const QLowEnergyHandle charHandle)
1189{
1190 Q_ASSERT(!service.isNull());
1191 if (!service->characteristicList.contains(charHandle)) {
1192 qCWarning(QT_BT_BLUEZ) << "Read characteristic does not belong to service"
1193 << service->uuid;
1194 return;
1195 }
1196
1197 const QLowEnergyServicePrivate::CharData &charDetails
1198 = service->characteristicList[charHandle];
1199 if (!(charDetails.properties & QLowEnergyCharacteristic::Read)) {
1200 // if this succeeds the device has a bug, char is advertised as
1201 // non-readable. We try to be permissive and let the remote
1202 // device answer to the read attempt
1203 qCWarning(QT_BT_BLUEZ) << "Reading non-readable char" << charHandle;
1204 }
1205
1206 const GattService &gattService = dbusServices[service->uuid];
1207 if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) {
1208 // Reread from dbus interface and write to local cache
1209 const QByteArray newValue(1, char(gattService.batteryInterface->percentage()));
1210 quint16 result = updateValueOfCharacteristic(charHandle, newValue, false);
1211 if (result > 0) {
1212 QLowEnergyCharacteristic ch(service, charHandle);
1213 emit service->characteristicRead(ch, newValue);
1214 } else {
1216 }
1217 return;
1218 }
1219
1220 GattJob job;
1221 job.flags = GattJob::JobFlags({GattJob::CharRead});
1222 job.service = service;
1223 job.handle = charHandle;
1224 jobs.append(job);
1225
1226 scheduleNextJob();
1227}
1228
1230 const QSharedPointer<QLowEnergyServicePrivate> service,
1231 const QLowEnergyHandle charHandle,
1232 const QLowEnergyHandle descriptorHandle)
1233{
1234 Q_ASSERT(!service.isNull());
1235 if (!service->characteristicList.contains(charHandle))
1236 return;
1237
1238 const QLowEnergyServicePrivate::CharData &charDetails
1239 = service->characteristicList[charHandle];
1240 if (!charDetails.descriptorList.contains(descriptorHandle))
1241 return;
1242
1243 const GattService &gattService = dbusServices[service->uuid];
1244 if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) {
1245 auto descriptor = descriptorForHandle(descriptorHandle);
1246 if (descriptor.isValid())
1247 emit service->descriptorRead(descriptor, descriptor.value());
1248 else
1249 service->setError(QLowEnergyService::DescriptorReadError);
1250
1251 return;
1252 }
1253
1254 GattJob job;
1255 job.flags = GattJob::JobFlags({GattJob::DescRead});
1256 job.service = service;
1257 job.handle = descriptorHandle;
1258 jobs.append(job);
1259
1260 scheduleNextJob();
1261}
1262
1264 const QSharedPointer<QLowEnergyServicePrivate> service,
1265 const QLowEnergyHandle charHandle,
1266 const QByteArray &newValue,
1268{
1269 Q_ASSERT(!service.isNull());
1270 if (!service->characteristicList.contains(charHandle)) {
1271 qCWarning(QT_BT_BLUEZ) << "Write characteristic does not belong to service"
1272 << service->uuid;
1273 return;
1274 }
1275
1277 const GattService &gattService = dbusServices[service->uuid];
1278 if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) {
1279 //Battery1 interface is readonly
1281 return;
1282 }
1283
1284
1285 GattJob job;
1286 job.flags = GattJob::JobFlags({GattJob::CharWrite});
1287 job.service = service;
1288 job.handle = charHandle;
1289 job.value = newValue;
1290 job.writeMode = writeMode;
1291 jobs.append(job);
1292
1293 scheduleNextJob();
1294 } else {
1295 // Peripheral role
1296 Q_ASSERT(peripheralApplication);
1297 if (!peripheralApplication->localCharacteristicWrite(charHandle, newValue)) {
1298 qCWarning(QT_BT_BLUEZ) << "Characteristic write failed"
1299 << characteristicForHandle(charHandle).uuid();
1301 return;
1302 }
1303 QLowEnergyServicePrivate::CharData &charData = service->characteristicList[charHandle];
1304 charData.value = newValue;
1305 }
1306}
1307
1309 const QSharedPointer<QLowEnergyServicePrivate> service,
1310 const QLowEnergyHandle charHandle,
1311 const QLowEnergyHandle descriptorHandle,
1312 const QByteArray &newValue)
1313{
1314 Q_ASSERT(!service.isNull());
1315 if (!service->characteristicList.contains(charHandle))
1316 return;
1317
1319 const GattService &gattService = dbusServices[service->uuid];
1320 if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) {
1321 auto descriptor = descriptorForHandle(descriptorHandle);
1322 if (!descriptor.isValid())
1323 return;
1324
1326 if (newValue == QByteArray::fromHex("0000")
1327 || newValue == QByteArray::fromHex("0100")
1328 || newValue == QByteArray::fromHex("0200")) {
1329 quint16 result = updateValueOfDescriptor(charHandle, descriptorHandle, newValue, false);
1330 if (result > 0)
1331 emit service->descriptorWritten(descriptor, newValue);
1332 else
1334
1335 }
1336 } else {
1337 service->setError(QLowEnergyService::DescriptorWriteError);
1338 }
1339
1340 return;
1341 }
1342
1343 GattJob job;
1344 job.flags = GattJob::JobFlags({GattJob::DescWrite});
1345 job.service = service;
1346 job.handle = descriptorHandle;
1347 job.value = newValue;
1348 jobs.append(job);
1349
1350 scheduleNextJob();
1351 } else {
1352 // Peripheral role
1353 Q_ASSERT(peripheralApplication);
1354
1355 auto desc = descriptorForHandle(descriptorHandle);
1357 qCWarning(QT_BT_BLUEZ) << "CCCD write not supported in peripheral role";
1358 service->setError(QLowEnergyService::DescriptorWriteError);
1359 return;
1360 } else if (!peripheralApplication->localDescriptorWrite(descriptorHandle, newValue)) {
1361 qCWarning(QT_BT_BLUEZ) << "Descriptor write failed" << desc.uuid();
1362 service->setError(QLowEnergyService::DescriptorWriteError);
1363 return;
1364 }
1365 service->characteristicList[charHandle].descriptorList[descriptorHandle].value = newValue;
1366 }
1367}
1368
1371 const QLowEnergyAdvertisingData &advertisingData,
1372 const QLowEnergyAdvertisingData &scanResponseData)
1373{
1376
1377 Q_ASSERT(peripheralApplication);
1378 Q_ASSERT(!adapterPathWithPeripheralSupport.isEmpty());
1379
1380 if (advertiser) {
1381 // Clear any previous advertiser in case advertising data has changed.
1382 // For clarity: this function is called only in 'Unconnected' state
1383 delete advertiser;
1384 advertiser = nullptr;
1385 }
1386 advertiser = new QLeDBusAdvertiser(params, advertisingData, scanResponseData,
1387 adapterPathWithPeripheralSupport, this);
1389 this, &QLowEnergyControllerPrivateBluezDBus::handleAdvertisingError);
1390
1392
1393 // First register the application to bluez if needed, and then start the advertisement.
1394 // The application registration may fail and is asynchronous => serialize the steps.
1395 // For clarity: advertisements can be used without any services, but registering such
1396 // application to Bluez would fail
1397 if (peripheralApplication->registrationNeeded())
1398 peripheralApplication->registerApplication();
1399 else
1400 advertiser->startAdvertising();
1401}
1402
1404{
1405 // This function is called only in Advertising state
1407 if (advertiser) {
1408 advertiser->stopAdvertising();
1409 delete advertiser;
1410 advertiser = nullptr;
1411 }
1412}
1413
1414void QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationRegistered()
1415{
1416 // Start the actual advertising now that the application is registered.
1417 // Check the state first in case user has called stopAdvertising() during
1418 // application registration
1419 if (advertiser && state == QLowEnergyController::AdvertisingState)
1420 advertiser->startAdvertising();
1421 else
1422 peripheralApplication->unregisterApplication();
1423}
1424
1425void QLowEnergyControllerPrivateBluezDBus::handlePeripheralCharacteristicValueUpdate(
1427{
1428 const auto characteristic = characteristicForHandle(handle);
1429 if (characteristic.d_ptr
1430 && updateValueOfCharacteristic(handle, value, false) == value.size()) {
1431 emit characteristic.d_ptr->characteristicChanged(characteristic, value);
1432 } else {
1433 qCWarning(QT_BT_BLUEZ) << "Remote characteristic write failed";
1434 }
1435}
1436
1437void QLowEnergyControllerPrivateBluezDBus::handlePeripheralDescriptorValueUpdate(
1438 QLowEnergyHandle characteristicHandle,
1439 QLowEnergyHandle descriptorHandle,
1440 const QByteArray& value)
1441{
1442 const auto descriptor = descriptorForHandle(descriptorHandle);
1443 if (descriptor.d_ptr && updateValueOfDescriptor(
1444 characteristicHandle, descriptorHandle, value, false) == value.size()) {
1445 emit descriptor.d_ptr->descriptorWritten(descriptor, value);
1446 } else {
1447 qCWarning(QT_BT_BLUEZ) << "Remote descriptor write failed";
1448 }
1449}
1450
1451void QLowEnergyControllerPrivateBluezDBus::handlePeripheralRemoteDeviceChanged(
1453 const QString& name,
1454 quint16 mtu)
1455{
1457 remoteName = name;
1458 remoteMtu = mtu;
1459}
1460
1461void QLowEnergyControllerPrivateBluezDBus::handleAdvertisingError()
1462{
1463 Q_ASSERT(peripheralApplication);
1464 qCWarning(QT_BT_BLUEZ) << "An advertising error occurred";
1467 peripheralApplication->unregisterApplication();
1468}
1469
1470void QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationError()
1471{
1472 qCWarning(QT_BT_BLUEZ) << "A Bluez peripheral application error occurred";
1475}
1476
1477void QLowEnergyControllerPrivateBluezDBus::handlePeripheralConnectivityChanged(bool connected)
1478{
1480 qCDebug(QT_BT_BLUEZ) << "Peripheral application connected change to:" << connected;
1481 if (connected) {
1483 } else {
1484 resetController();
1485 remoteDevice.clear();
1487 emit q->disconnected();
1488 }
1489}
1490
1492 const QLowEnergyConnectionParameters & /* params */)
1493{
1494 qCWarning(QT_BT_BLUEZ) << "Connection udpate requests not supported on Bluez DBus";
1495}
1496
1498 const QLowEnergyServiceData &serviceData,
1499 QLowEnergyHandle startHandle)
1500{
1501 Q_ASSERT(peripheralApplication);
1502 QSharedPointer<QLowEnergyServicePrivate> servicePrivate = serviceForHandle(startHandle);
1503 if (servicePrivate.isNull())
1504 return;
1505 peripheralApplication->addService(serviceData, servicePrivate, startHandle);
1506}
1507
1509{
1510 // currently only supported on peripheral role
1511 return remoteMtu;
1512}
1513
1515
1516#include "moc_qlowenergycontroller_bluezdbus_p.cpp"
QString findAdapterForAddress(const QBluetoothAddress &wantedAddress, bool *ok=nullptr)
QMap< QDBusObjectPath, InterfaceList > ManagedObjectList
bool connected
IOBluetoothDevice * device
QDBusPendingReply< ManagedObjectList > GetManagedObjects()
void InterfacesRemoved(const QDBusObjectPath &object_path, const QStringList &interfaces)
void PropertiesChanged(const QString &interface, const QVariantMap &changed_properties, const QStringList &invalidated_properties, const QDBusMessage &msg)
\inmodule QtBluetooth
\inmodule QtBluetooth
\inmodule QtCore
Definition qbytearray.h:57
static QByteArray fromHex(const QByteArray &hexEncoded)
Returns a decoded copy of the hex encoded array hexEncoded.
QString path() const
Returns the object path that this interface is associated with.
static QDBusConnection systemBus()
Returns a QDBusConnection object opened with the system bus.
\inmodule QtDBus
QString path() const
Returns this object path.
void finished(QDBusPendingCallWatcher *self=nullptr)
This signal is emitted when the pending call has finished and its reply is available.
\inmodule QtCore
Definition qhash.h:1103
bool remove(const Key &key)
Removes the item that has the key from the hash.
Definition qhash.h:958
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:1007
void clear() noexcept(std::is_nothrow_destructible< Node >::value)
Removes all items from the hash and frees up all memory used by it.
Definition qhash.h:951
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1303
bool isEmpty() const noexcept
Definition qlist.h:401
T & last()
Definition qlist.h:648
value_type takeFirst()
Definition qlist.h:566
const T & constFirst() const noexcept
Definition qlist.h:647
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
The QLowEnergyAdvertisingData class represents the data to be broadcast during Bluetooth Low Energy a...
The QLowEnergyAdvertisingParameters class represents the parameters used for Bluetooth Low Energy adv...
QBluetoothUuid uuid() const
Returns the UUID of the characteristic if \l isValid() returns true; otherwise a \l {QUuid::isNull()}...
The QLowEnergyConnectionParameters class is used when requesting or reporting an update of the parame...
void requestConnectionUpdate(const QLowEnergyConnectionParameters &params) override
void startAdvertising(const QLowEnergyAdvertisingParameters &params, const QLowEnergyAdvertisingData &advertisingData, const QLowEnergyAdvertisingData &scanResponseData) override
void discoverServiceDetails(const QBluetoothUuid &service, QLowEnergyService::DiscoveryMode mode) override
void addToGenericAttributeList(const QLowEnergyServiceData &service, QLowEnergyHandle startHandle) override
void writeCharacteristic(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QByteArray &newValue, QLowEnergyService::WriteMode writeMode) override
void writeDescriptor(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QLowEnergyHandle descriptorHandle, const QByteArray &newValue) override
void readDescriptor(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QLowEnergyHandle descriptorHandle) override
void readCharacteristic(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle) override
QLowEnergyCharacteristic characteristicForHandle(QLowEnergyHandle handle)
Returns a valid characteristic if the given handle is the handle of the characteristic itself or one ...
QSharedPointer< QLowEnergyServicePrivate > serviceForHandle(QLowEnergyHandle handle)
quint16 updateValueOfDescriptor(QLowEnergyHandle charHandle, QLowEnergyHandle descriptorHandle, const QByteArray &value, bool appendValue)
Returns the length of the updated descriptor value.
QLowEnergyDescriptor descriptorForHandle(QLowEnergyHandle handle)
Returns a valid descriptor if handle belongs to a descriptor; otherwise an invalid one.
QLowEnergyController::Error error
void setError(QLowEnergyController::Error newError)
quint16 updateValueOfCharacteristic(QLowEnergyHandle charHandle, const QByteArray &value, bool appendValue)
Returns the length of the updated characteristic value.
QLowEnergyController::ControllerState state
void setState(QLowEnergyController::ControllerState newState)
\inmodule QtBluetooth
Error
Indicates all possible error conditions found during the controller's existence.
\inmodule QtBluetooth
QBluetoothUuid uuid() const
Returns the UUID of this descriptor if \l isValid() returns true; otherwise a \l {QUuid::isNull()}{nu...
QByteArray value() const
Returns the cached value of the descriptor.
bool isValid() const
Returns true if the QLowEnergyDescriptor object is valid, otherwise returns false.
The QLowEnergyServiceData class is used to set up GATT service data. \inmodule QtBluetooth.
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)
DiscoveryMode
This enum lists service discovery modes.
WriteMode
This enum describes the mode to be used when writing a characteristic value.
ServiceType
This enum describes the type of the service.
T value(const Key &key, const T &defaultValue=T()) const
Definition qmap.h:357
bool contains(const Key &key) const
Definition qmap.h:341
NetworkError error() const
Returns the error that was found during the processing of this request.
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
const_iterator constBegin() const noexcept
Definition qset.h:139
const_iterator constEnd() const noexcept
Definition qset.h:143
static QSharedPointer create(Args &&...arguments)
This is an overloaded member function, provided for convenience. It differs from the above function o...
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1252
\inmodule QtCore
Definition qvariant.h:65
T value() const &
Definition qvariant.h:516
bool toBool() const
Returns the variant as a bool if the variant has userType() Bool.
QByteArray toByteArray() const
Returns the variant as a QByteArray if the variant has userType() \l QMetaType::QByteArray or \l QMet...
QStringList toStringList() const
Returns the variant as a QStringList if the variant has userType() \l QMetaType::QStringList,...
bool localDescriptorWrite(QLowEnergyHandle handle, const QByteArray &value)
void remoteDeviceAccessEvent(const QString &remoteDeviceObjectPath, quint16 mtu)
void addService(const QLowEnergyServiceData &serviceData, QSharedPointer< QLowEnergyServicePrivate > servicePrivate, QLowEnergyHandle serviceHandle)
void characteristicValueUpdatedByRemote(QLowEnergyHandle handle, const QByteArray &value)
bool localCharacteristicWrite(QLowEnergyHandle handle, const QByteArray &value)
void descriptorValueUpdatedByRemote(QLowEnergyHandle characteristicHandle, QLowEnergyHandle descriptorHandle, const QByteArray &value)
void connectivityStateChanged(bool connected)
void remoteDeviceAccessEvent(const QString &remoteDeviceObjectPath, quint16 mtu)
void remoteDeviceChanged(const QBluetoothAddress &address, const QString &name, quint16 mtu)
list append(new Employee("Blackpool", "Stephen"))
QSet< QString >::iterator it
Combined button and popup list for selecting options.
Q_CORE_EXPORT QtJniTypes::Service service()
constexpr QBindableInterface iface
Definition qproperty.h:666
quint16 QLowEnergyHandle
Definition qbluetooth.h:42
QMap< QString, QVariant > QVariantMap
static const QCssKnownValue properties[NumProperties - 1]
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter * iter
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char * interface
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
static const QMetaObjectPrivate * priv(const uint *data)
GLuint64 GLenum void * handle
GLenum mode
GLenum type
GLuint name
void ** params
GLuint entry
GLuint GLuint64EXT address
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define QStringLiteral(str)
#define emit
unsigned short quint16
Definition qtypes.h:48
QFutureWatcher< int > watcher
QStringList keys
QNetworkAccessManager manager
QNetworkReply * reply