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

source code of qtconnectivity/src/bluetooth/qlowenergycontroller_bluezdbus.cpp