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