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