1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtBluetooth module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qlowenergycontroller_bluezdbus_p.h"
41#include "bluez/adapter1_bluez5_p.h"
42#include "bluez/bluez5_helper_p.h"
43#include "bluez/device1_bluez5_p.h"
44#include "bluez/gattservice1_p.h"
45#include "bluez/gattchar1_p.h"
46#include "bluez/gattdesc1_p.h"
47#include "bluez/battery1_p.h"
48#include "bluez/objectmanager_p.h"
49#include "bluez/properties_p.h"
50
51
52QT_BEGIN_NAMESPACE
53
54Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
55
56QLowEnergyControllerPrivateBluezDBus::QLowEnergyControllerPrivateBluezDBus()
57 : QLowEnergyControllerPrivate()
58{
59}
60
61QLowEnergyControllerPrivateBluezDBus::~QLowEnergyControllerPrivateBluezDBus()
62{
63}
64
65void QLowEnergyControllerPrivateBluezDBus::init()
66{
67}
68
69void QLowEnergyControllerPrivateBluezDBus::devicePropertiesChanged(
70 const QString &interface, const QVariantMap &changedProperties,
71 const QStringList &/*removedProperties*/)
72{
73 if (interface == QStringLiteral("org.bluez.Device1")) {
74 qCDebug(QT_BT_BLUEZ) << "######" << interface << changedProperties;
75 if (changedProperties.contains(QStringLiteral("ServicesResolved"))) {
76 //we could check for Connected property as well, but we choose to wait
77 //for ServicesResolved being true
78
79 if (pendingConnect) {
80 bool isResolved = changedProperties.value(QStringLiteral("ServicesResolved")).toBool();
81 if (isResolved) {
82 setState(QLowEnergyController::ConnectedState);
83 pendingConnect = false;
84 disconnectSignalRequired = true;
85 Q_Q(QLowEnergyController);
86 emit q->connected();
87 }
88 }
89 }
90
91 if (changedProperties.contains(QStringLiteral("Connected"))) {
92 bool isConnected = changedProperties.value(QStringLiteral("Connected")).toBool();
93 if (!isConnected) {
94 switch (state) {
95 case QLowEnergyController::ConnectingState:
96 case QLowEnergyController::ConnectedState:
97 case QLowEnergyController::DiscoveringState:
98 case QLowEnergyController::DiscoveredState:
99 case QLowEnergyController::ClosingState:
100 {
101 QLowEnergyController::Error newError = QLowEnergyController::NoError;
102 if (pendingConnect)
103 newError = QLowEnergyController::ConnectionError;
104
105 executeClose(newError);
106 }
107 break;
108 case QLowEnergyController::AdvertisingState:
109 case QLowEnergyController::UnconnectedState:
110 //ignore
111 break;
112 }
113 }
114 }
115 } else if (interface == QStringLiteral("org.bluez.Battery1")) {
116 qCDebug(QT_BT_BLUEZ) << "######" << interface << changedProperties;
117 if (changedProperties.contains(QStringLiteral("Percentage"))) {
118 // if battery service is discovered and ClientCharConfig is enabled
119 // emit characteristicChanged() signal
120 const QBluetoothUuid uuid(QBluetoothUuid::BatteryService);
121 if (!serviceList.contains(uuid) || !dbusServices.contains(uuid)
122 || !dbusServices[uuid].hasBatteryService
123 || dbusServices[uuid].batteryInterface.isNull())
124 return;
125
126 QSharedPointer<QLowEnergyServicePrivate> serviceData = serviceList.value(uuid);
127 if (serviceData->state != QLowEnergyService::ServiceDiscovered)
128 return;
129
130 QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData>::iterator iter;
131 iter = serviceData->characteristicList.begin();
132 while (iter != serviceData->characteristicList.end()) {
133 auto &charData = iter.value();
134 if (charData.uuid != QBluetoothUuid::BatteryLevel)
135 continue;
136
137 // Client Characteristic Notification enabled?
138 bool cccActive = false;
139 for (const QLowEnergyServicePrivate::DescData &descData : qAsConst(charData.descriptorList)) {
140 if (descData.uuid != QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration))
141 continue;
142 if (descData.value == QByteArray::fromHex("0100")
143 || descData.value == QByteArray::fromHex("0200")) {
144 cccActive = true;
145 break;
146 }
147 }
148
149 const QByteArray newValue(1, char(dbusServices[uuid].batteryInterface->percentage()));
150 qCDebug(QT_BT_BLUEZ) << "Battery1 char update" << cccActive
151 << charData.value.toHex() << "->" << newValue.toHex();
152 if (cccActive && newValue != charData.value) {
153 qCDebug(QT_BT_BLUEZ) << "Property update for Battery1";
154 charData.value = newValue;
155 QLowEnergyCharacteristic ch(serviceData, iter.key());
156 emit serviceData->characteristicChanged(ch, newValue);
157 }
158
159 break;
160 }
161 }
162 }
163}
164
165void QLowEnergyControllerPrivateBluezDBus::characteristicPropertiesChanged(
166 QLowEnergyHandle charHandle, const QString &interface,
167 const QVariantMap &changedProperties,
168 const QStringList &/*removedProperties*/)
169{
170 //qCDebug(QT_BT_BLUEZ) << "$$$$$$$$$$$$$$$$$$ char monitor"
171 // << interface << changedProperties << charHandle;
172 if (interface != QStringLiteral("org.bluez.GattCharacteristic1"))
173 return;
174
175 if (!changedProperties.contains(QStringLiteral("Value")))
176 return;
177
178 const QLowEnergyCharacteristic changedChar = characteristicForHandle(charHandle);
179 const QLowEnergyDescriptor ccnDescriptor = changedChar.descriptor(
180 QBluetoothUuid::ClientCharacteristicConfiguration);
181 if (!ccnDescriptor.isValid())
182 return;
183
184 const QByteArray newValue = changedProperties.value(QStringLiteral("Value")).toByteArray();
185 if (changedChar.properties() & QLowEnergyCharacteristic::Read)
186 updateValueOfCharacteristic(charHandle, newValue, false); //TODO upgrade to NEW_VALUE/APPEND_VALUE
187
188 auto service = serviceForHandle(charHandle);
189
190 if (!service.isNull())
191 emit service->characteristicChanged(changedChar, newValue);
192}
193
194void QLowEnergyControllerPrivateBluezDBus::interfacesRemoved(
195 const QDBusObjectPath &objectPath, const QStringList &/*interfaces*/)
196{
197 if (objectPath.path() == device->path()) {
198 qCWarning(QT_BT_BLUEZ) << "DBus Device1 was removed";
199 executeClose(QLowEnergyController::UnknownRemoteDeviceError);
200 } else if (objectPath.path() == adapter->path()) {
201 qCWarning(QT_BT_BLUEZ) << "DBus Adapter was removed";
202 executeClose(QLowEnergyController::InvalidBluetoothAdapterError);
203 }
204}
205
206void QLowEnergyControllerPrivateBluezDBus::resetController()
207{
208 if (managerBluez) {
209 delete managerBluez;
210 managerBluez = nullptr;
211 }
212
213 if (adapter) {
214 delete adapter;
215 adapter = nullptr;
216 }
217
218 if (device) {
219 delete device;
220 device = nullptr;
221 }
222
223 if (deviceMonitor) {
224 delete deviceMonitor;
225 deviceMonitor = nullptr;
226 }
227
228 dbusServices.clear();
229 jobs.clear();
230 invalidateServices();
231
232 pendingConnect = disconnectSignalRequired = false;
233 jobPending = false;
234}
235
236void QLowEnergyControllerPrivateBluezDBus::connectToDeviceHelper()
237{
238 resetController();
239
240 bool ok = false;
241 const QString hostAdapterPath = findAdapterForAddress(localAdapter, &ok);
242 if (!ok || hostAdapterPath.isEmpty()) {
243 qCWarning(QT_BT_BLUEZ) << "Cannot find suitable bluetooth adapter";
244 setError(QLowEnergyController::InvalidBluetoothAdapterError);
245 return;
246 }
247
248 QScopedPointer<OrgFreedesktopDBusObjectManagerInterface> manager(
249 new OrgFreedesktopDBusObjectManagerInterface(
250 QStringLiteral("org.bluez"), QStringLiteral("/"),
251 QDBusConnection::systemBus()));
252
253 QDBusPendingReply<ManagedObjectList> reply = manager->GetManagedObjects();
254 reply.waitForFinished();
255 if (reply.isError()) {
256 qCWarning(QT_BT_BLUEZ) << "Cannot enumerate Bluetooth devices for GATT connect";
257 setError(QLowEnergyController::ConnectionError);
258 return;
259 }
260
261 QString devicePath;
262 ManagedObjectList managedObjectList = reply.value();
263 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
264 const InterfaceList &ifaceList = it.value();
265
266 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
267 const QString &iface = jt.key();
268 const QVariantMap &ifaceValues = jt.value();
269
270 if (iface == QStringLiteral("org.bluez.Device1")) {
271 if (remoteDevice.toString() == ifaceValues.value(QStringLiteral("Address")).toString())
272 {
273 const QVariant adapterForCurrentDevice = ifaceValues.value(QStringLiteral("Adapter"));
274 if (qvariant_cast<QDBusObjectPath>(adapterForCurrentDevice).path() == hostAdapterPath) {
275 devicePath = it.key().path();
276 break;
277 }
278 }
279 }
280 }
281
282 if (!devicePath.isEmpty())
283 break;
284 }
285
286 if (devicePath.isEmpty()) {
287 qCDebug(QT_BT_BLUEZ) << "Cannot find targeted remote device. "
288 "Re-running device discovery might help";
289 setError(QLowEnergyController::UnknownRemoteDeviceError);
290 return;
291 }
292
293 managerBluez = manager.take();
294 connect(managerBluez, &OrgFreedesktopDBusObjectManagerInterface::InterfacesRemoved,
295 this, &QLowEnergyControllerPrivateBluezDBus::interfacesRemoved);
296 adapter = new OrgBluezAdapter1Interface(
297 QStringLiteral("org.bluez"), hostAdapterPath,
298 QDBusConnection::systemBus(), this);
299 device = new OrgBluezDevice1Interface(
300 QStringLiteral("org.bluez"), devicePath,
301 QDBusConnection::systemBus(), this);
302 deviceMonitor = new OrgFreedesktopDBusPropertiesInterface(
303 QStringLiteral("org.bluez"), devicePath,
304 QDBusConnection::systemBus(), this);
305 connect(deviceMonitor, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged,
306 this, &QLowEnergyControllerPrivateBluezDBus::devicePropertiesChanged);
307}
308
309void QLowEnergyControllerPrivateBluezDBus::connectToDevice()
310{
311 qCDebug(QT_BT_BLUEZ) << "QLowEnergyControllerPrivateBluezDBus::connectToDevice()";
312
313 connectToDeviceHelper();
314
315 if (!adapter || !device)
316 return;
317
318 if (!adapter->powered()) {
319 qCWarning(QT_BT_BLUEZ) << "Error: Local adapter is powered off";
320 setError(QLowEnergyController::ConnectionError);
321 return;
322 }
323
324 setState(QLowEnergyController::ConnectingState);
325
326 //Bluez interface is shared among all platform processes
327 //and hence we might be connected already
328 if (device->connected() && device->servicesResolved()) {
329 //connectToDevice is noop
330 disconnectSignalRequired = true;
331
332 setState(QLowEnergyController::ConnectedState);
333 Q_Q(QLowEnergyController);
334 emit q->connected();
335 return;
336 }
337
338 pendingConnect = true;
339
340 QDBusPendingReply<> reply = device->Connect();
341 QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
342 connect(watcher, &QDBusPendingCallWatcher::finished, this,
343 [=](QDBusPendingCallWatcher* call) {
344 QDBusPendingReply<> reply = *call;
345 if (reply.isError()) {
346 qCDebug(QT_BT_BLUEZ) << "BTLE_DBUS::connect() failed"
347 << reply.reply().errorName()
348 << reply.reply().errorMessage();
349 executeClose(QLowEnergyController::UnknownError);
350 } // else -> connected when Connected property is set to true (see devicePropertiesChanged())
351 call->deleteLater();
352 });
353}
354
355void QLowEnergyControllerPrivateBluezDBus::disconnectFromDevice()
356{
357 if (!device)
358 return;
359
360 setState(QLowEnergyController::ClosingState);
361
362 QDBusPendingReply<> reply = device->Disconnect();
363 QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
364 connect(watcher, &QDBusPendingCallWatcher::finished, this,
365 [=](QDBusPendingCallWatcher* call) {
366 QDBusPendingReply<> reply = *call;
367 if (reply.isError()) {
368 qCDebug(QT_BT_BLUEZ) << "BTLE_DBUS::disconnect() failed"
369 << reply.reply().errorName()
370 << reply.reply().errorMessage();
371 executeClose(QLowEnergyController::NoError);
372 }
373 call->deleteLater();
374 });
375}
376
377void QLowEnergyControllerPrivateBluezDBus::discoverServices()
378{
379 QDBusPendingReply<ManagedObjectList> reply = managerBluez->GetManagedObjects();
380 reply.waitForFinished();
381 if (reply.isError()) {
382 qCWarning(QT_BT_BLUEZ) << "Cannot discover services";
383 setError(QLowEnergyController::UnknownError);
384 setState(QLowEnergyController::DiscoveredState);
385 return;
386 }
387
388 Q_Q(QLowEnergyController);
389
390 auto setupServicePrivate = [&, q](
391 QLowEnergyService::ServiceType type, const QBluetoothUuid &uuid, const QString &path){
392 QSharedPointer<QLowEnergyServicePrivate> priv = QSharedPointer<QLowEnergyServicePrivate>::create();
393 priv->uuid = uuid;
394 priv->type = type; // we make a guess we cannot validate
395 priv->setController(this);
396
397 GattService serviceContainer;
398 serviceContainer.servicePath = path;
399 if (uuid == QBluetoothUuid::BatteryService)
400 serviceContainer.hasBatteryService = true;
401
402 serviceList.insert(priv->uuid, priv);
403 dbusServices.insert(priv->uuid, serviceContainer);
404
405 emit q->serviceDiscovered(priv->uuid);
406 };
407
408 const ManagedObjectList managedObjectList = reply.value();
409 const QString servicePathPrefix = device->path().append(QStringLiteral("/service"));
410 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
411 const InterfaceList &ifaceList = it.value();
412
413 if (!it.key().path().startsWith(device->path()))
414 continue;
415
416 // Since Bluez 5.48 battery services (0x180f) are no longer exposed
417 // as generic services under servicePathPrefix.
418 // A dedicated org.bluez.Battery1 interface is exposed. Here we are going to revert
419 // Bettery1 to the generic pattern.
420 if (it.key().path() == device->path()) {
421 // find Battery1 service
422 for (InterfaceList::const_iterator battIter = ifaceList.constBegin(); battIter != ifaceList.constEnd(); ++battIter) {
423 const QString &iface = battIter.key();
424 if (iface == QStringLiteral("org.bluez.Battery1")) {
425 qCDebug(QT_BT_BLUEZ) << "Found dedicated Battery service -> emulating generic btle access";
426 setupServicePrivate(QLowEnergyService::PrimaryService,
427 QBluetoothUuid::BatteryService,
428 it.key().path());
429 }
430 }
431 continue;
432 }
433
434 if (!it.key().path().startsWith(servicePathPrefix))
435 continue;
436
437 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
438 const QString &iface = jt.key();
439
440 if (iface == QStringLiteral("org.bluez.GattService1")) {
441 QScopedPointer<OrgBluezGattService1Interface> service(new OrgBluezGattService1Interface(
442 QStringLiteral("org.bluez"),it.key().path(),
443 QDBusConnection::systemBus(), this));
444 setupServicePrivate(service->primary()
445 ? QLowEnergyService::PrimaryService
446 : QLowEnergyService::IncludedService,
447 QBluetoothUuid(service->uUID()), it.key().path());
448 }
449 }
450 }
451
452 setState(QLowEnergyController::DiscoveredState);
453 emit q->discoveryFinished();
454}
455
456void QLowEnergyControllerPrivateBluezDBus::discoverBatteryServiceDetails(
457 GattService &dbusData, QSharedPointer<QLowEnergyServicePrivate> serviceData)
458{
459 // This process exists to work around the fact that Battery services (0x180f)
460 // are not mapped as generic services but use the Battery1 interface.
461 // Artificial chararacteristics and descriptors are created to emulate the generic behavior.
462
463 auto batteryService = QSharedPointer<OrgBluezBattery1Interface>::create(
464 QStringLiteral("org.bluez"), dbusData.servicePath,
465 QDBusConnection::systemBus());
466 dbusData.batteryInterface = batteryService;
467
468 serviceData->startHandle = runningHandle++; //service start handle
469
470 // Create BatteryLevel char
471 QLowEnergyHandle indexHandle = runningHandle++; // char handle index
472 QLowEnergyServicePrivate::CharData charData;
473
474 charData.valueHandle = runningHandle++;
475 charData.properties.setFlag(QLowEnergyCharacteristic::Read);
476 charData.properties.setFlag(QLowEnergyCharacteristic::Notify);
477 charData.uuid = QBluetoothUuid::BatteryLevel;
478 charData.value = QByteArray(1, char(batteryService->percentage()));
479
480 // Create the descriptors for the BatteryLevel
481 // They are hardcoded although CCC may change
482 QLowEnergyServicePrivate::DescData descData;
483 QLowEnergyHandle descriptorHandle = runningHandle++;
484 descData.uuid = QBluetoothUuid::ClientCharacteristicConfiguration;
485 descData.value = QByteArray::fromHex("0000"); // all configs off
486 charData.descriptorList.insert(descriptorHandle, descData);
487
488 descriptorHandle = runningHandle++;
489 descData.uuid = QBluetoothUuid::CharacteristicPresentationFormat;
490 //for details see Characteristic Presentation Format Vol3, Part G 3.3.3.5
491 // unsigend 8 bit, exp=1, org.bluetooth.unit.percentage, namespace & description
492 // bit order: little endian
493 descData.value = QByteArray::fromHex("0400ad27011131");
494 charData.descriptorList.insert(descriptorHandle, descData);
495
496 descriptorHandle = runningHandle++;
497 descData.uuid = QBluetoothUuid::ReportReference;
498 descData.value = QByteArray::fromHex("0401");
499 charData.descriptorList.insert(descriptorHandle, descData);
500
501 serviceData->characteristicList[indexHandle] = charData;
502 serviceData->endHandle = runningHandle++;
503
504 serviceData->setState(QLowEnergyService::ServiceDiscovered);
505}
506
507void QLowEnergyControllerPrivateBluezDBus::executeClose(QLowEnergyController::Error newError)
508{
509 const bool emitDisconnect = disconnectSignalRequired;
510
511 resetController();
512 if (newError != QLowEnergyController::NoError)
513 setError(newError);
514
515 setState(QLowEnergyController::UnconnectedState);
516 if (emitDisconnect) {
517 Q_Q(QLowEnergyController);
518 emit q->disconnected();
519 }
520}
521
522void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoothUuid &service)
523{
524 if (!serviceList.contains(service) || !dbusServices.contains(service)) {
525 qCWarning(QT_BT_BLUEZ) << "Discovery of unknown service" << service.toString()
526 << "not possible";
527 return;
528 }
529
530 //clear existing service data and run new discovery
531 QSharedPointer<QLowEnergyServicePrivate> serviceData = serviceList.value(service);
532 serviceData->characteristicList.clear();
533
534 GattService &dbusData = dbusServices[service];
535 dbusData.characteristics.clear();
536
537 if (dbusData.hasBatteryService) {
538 qCDebug(QT_BT_BLUEZ) << "Triggering Battery1 service discovery on " << dbusData.servicePath;
539 discoverBatteryServiceDetails(dbusData, serviceData);
540 return;
541 }
542
543 QDBusPendingReply<ManagedObjectList> reply = managerBluez->GetManagedObjects();
544 reply.waitForFinished();
545 if (reply.isError()) {
546 qCWarning(QT_BT_BLUEZ) << "Cannot discover services";
547 setError(QLowEnergyController::UnknownError);
548 setState(QLowEnergyController::DiscoveredState);
549 return;
550 }
551
552 QStringList descriptorPaths;
553 const ManagedObjectList managedObjectList = reply.value();
554 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
555 const InterfaceList &ifaceList = it.value();
556 if (!it.key().path().startsWith(dbusData.servicePath))
557 continue;
558
559 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
560 const QString &iface = jt.key();
561 if (iface == QStringLiteral("org.bluez.GattCharacteristic1")) {
562 auto charInterface = QSharedPointer<OrgBluezGattCharacteristic1Interface>::create(
563 QStringLiteral("org.bluez"), it.key().path(),
564 QDBusConnection::systemBus());
565 GattCharacteristic dbusCharData;
566 dbusCharData.characteristic = charInterface;
567 dbusData.characteristics.append(dbusCharData);
568 } else if (iface == QStringLiteral("org.bluez.GattDescriptor1")) {
569 auto descInterface = QSharedPointer<OrgBluezGattDescriptor1Interface>::create(
570 QStringLiteral("org.bluez"), it.key().path(),
571 QDBusConnection::systemBus());
572 bool found = false;
573 for (GattCharacteristic &dbusCharData : dbusData.characteristics) {
574 if (!descInterface->path().startsWith(
575 dbusCharData.characteristic->path()))
576 continue;
577
578 found = true;
579 dbusCharData.descriptors.append(descInterface);
580 break;
581 }
582
583 Q_ASSERT(found);
584 if (!found)
585 qCWarning(QT_BT_BLUEZ) << "Descriptor discovery error";
586 }
587 }
588 }
589
590 //populate servicePrivate based on dbus data
591 serviceData->startHandle = runningHandle++;
592 for (GattCharacteristic &dbusChar : dbusData.characteristics) {
593 const QLowEnergyHandle indexHandle = runningHandle++;
594 QLowEnergyServicePrivate::CharData charData;
595
596 // characteristic data
597 charData.valueHandle = runningHandle++;
598 const QStringList properties = dbusChar.characteristic->flags();
599
600 for (const auto &entry : properties) {
601 if (entry == QStringLiteral("broadcast"))
602 charData.properties.setFlag(QLowEnergyCharacteristic::Broadcasting, true);
603 else if (entry == QStringLiteral("read"))
604 charData.properties.setFlag(QLowEnergyCharacteristic::Read, true);
605 else if (entry == QStringLiteral("write-without-response"))
606 charData.properties.setFlag(QLowEnergyCharacteristic::WriteNoResponse, true);
607 else if (entry == QStringLiteral("write"))
608 charData.properties.setFlag(QLowEnergyCharacteristic::Write, true);
609 else if (entry == QStringLiteral("notify"))
610 charData.properties.setFlag(QLowEnergyCharacteristic::Notify, true);
611 else if (entry == QStringLiteral("indicate"))
612 charData.properties.setFlag(QLowEnergyCharacteristic::Indicate, true);
613 else if (entry == QStringLiteral("authenticated-signed-writes"))
614 charData.properties.setFlag(QLowEnergyCharacteristic::WriteSigned, true);
615 else if (entry == QStringLiteral("reliable-write"))
616 charData.properties.setFlag(QLowEnergyCharacteristic::ExtendedProperty, true);
617 else if (entry == QStringLiteral("writable-auxiliaries"))
618 charData.properties.setFlag(QLowEnergyCharacteristic::ExtendedProperty, true);
619 //all others ignored - not relevant for this API
620 }
621
622 charData.uuid = QBluetoothUuid(dbusChar.characteristic->uUID());
623
624 // schedule read for initial char value
625 if (charData.properties.testFlag(QLowEnergyCharacteristic::Read)) {
626 GattJob job;
627 job.flags = GattJob::JobFlags({GattJob::CharRead, GattJob::ServiceDiscovery});
628 job.service = serviceData;
629 job.handle = indexHandle;
630 jobs.append(job);
631 }
632
633 // descriptor data
634 for (const auto &descEntry : qAsConst(dbusChar.descriptors)) {
635 const QLowEnergyHandle descriptorHandle = runningHandle++;
636 QLowEnergyServicePrivate::DescData descData;
637 descData.uuid = QBluetoothUuid(descEntry->uUID());
638 charData.descriptorList.insert(descriptorHandle, descData);
639
640
641 // every ClientCharacteristicConfiguration needs to track property changes
642 if (descData.uuid
643 == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) {
644 dbusChar.charMonitor = QSharedPointer<OrgFreedesktopDBusPropertiesInterface>::create(
645 QStringLiteral("org.bluez"),
646 dbusChar.characteristic->path(),
647 QDBusConnection::systemBus(), this);
648 connect(dbusChar.charMonitor.data(), &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged,
649 this, [this, indexHandle](const QString &interface, const QVariantMap &changedProperties,
650 const QStringList &removedProperties) {
651
652 characteristicPropertiesChanged(indexHandle, interface,
653 changedProperties, removedProperties);
654 });
655 }
656
657 // schedule read for initial descriptor value
658 GattJob job;
659 job.flags = GattJob::JobFlags({GattJob::DescRead, GattJob::ServiceDiscovery});
660 job.service = serviceData;
661 job.handle = descriptorHandle;
662 jobs.append(job);
663 }
664
665 serviceData->characteristicList[indexHandle] = charData;
666 }
667
668 serviceData->endHandle = runningHandle++;
669
670 // last job is last step of service discovery
671 if (!jobs.isEmpty()) {
672 GattJob &lastJob = jobs.last();
673 lastJob.flags.setFlag(GattJob::LastServiceDiscovery, true);
674 } else {
675 serviceData->setState(QLowEnergyService::ServiceDiscovered);
676 }
677
678 scheduleNextJob();
679}
680
681void QLowEnergyControllerPrivateBluezDBus::prepareNextJob()
682{
683 jobs.takeFirst(); // finish last job
684 jobPending = false;
685
686 scheduleNextJob(); // continue with next job - if available
687}
688
689void QLowEnergyControllerPrivateBluezDBus::onCharReadFinished(QDBusPendingCallWatcher *call)
690{
691 if (!jobPending || jobs.isEmpty()) {
692 // this may happen when service disconnects before dbus watcher returns later on
693 qCWarning(QT_BT_BLUEZ) << "Aborting onCharReadFinished due to disconnect";
694 Q_ASSERT(state == QLowEnergyController::UnconnectedState);
695 return;
696 }
697
698 const GattJob nextJob = jobs.constFirst();
699 Q_ASSERT(nextJob.flags.testFlag(GattJob::CharRead));
700
701 QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(nextJob.handle);
702 if (service.isNull() || !dbusServices.contains(service->uuid)) {
703 qCWarning(QT_BT_BLUEZ) << "onCharReadFinished: Invalid GATT job. Skipping.";
704 call->deleteLater();
705 prepareNextJob();
706 return;
707 }
708 const QLowEnergyServicePrivate::CharData &charData =
709 service->characteristicList.value(nextJob.handle);
710
711 bool isServiceDiscovery = nextJob.flags.testFlag(GattJob::ServiceDiscovery);
712 QDBusPendingReply<QByteArray> reply = *call;
713 if (reply.isError()) {
714 qCWarning(QT_BT_BLUEZ) << "Cannot initiate reading of" << charData.uuid
715 << "of service" << service->uuid
716 << reply.error().name() << reply.error().message();
717 if (!isServiceDiscovery)
718 service->setError(QLowEnergyService::CharacteristicReadError);
719 } else {
720 qCDebug(QT_BT_BLUEZ) << "Read Char:" << charData.uuid << reply.value().toHex();
721 if (charData.properties.testFlag(QLowEnergyCharacteristic::Read))
722 updateValueOfCharacteristic(nextJob.handle, reply.value(), false);
723
724 if (isServiceDiscovery) {
725 if (nextJob.flags.testFlag(GattJob::LastServiceDiscovery))
726 service->setState(QLowEnergyService::ServiceDiscovered);
727 } else {
728 QLowEnergyCharacteristic ch(service, nextJob.handle);
729 emit service->characteristicRead(ch, reply.value());
730 }
731 }
732
733 call->deleteLater();
734 prepareNextJob();
735}
736
737void QLowEnergyControllerPrivateBluezDBus::onDescReadFinished(QDBusPendingCallWatcher *call)
738{
739 if (!jobPending || jobs.isEmpty()) {
740 // this may happen when service disconnects before dbus watcher returns later on
741 qCWarning(QT_BT_BLUEZ) << "Aborting onDescReadFinished due to disconnect";
742 Q_ASSERT(state == QLowEnergyController::UnconnectedState);
743 return;
744 }
745
746 const GattJob nextJob = jobs.constFirst();
747 Q_ASSERT(nextJob.flags.testFlag(GattJob::DescRead));
748
749 QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(nextJob.handle);
750 if (service.isNull() || !dbusServices.contains(service->uuid)) {
751 qCWarning(QT_BT_BLUEZ) << "onDescReadFinished: Invalid GATT job. Skipping.";
752 call->deleteLater();
753 prepareNextJob();
754 return;
755 }
756
757 QLowEnergyCharacteristic ch = characteristicForHandle(nextJob.handle);
758 if (!ch.isValid()) {
759 qCWarning(QT_BT_BLUEZ) << "Cannot find char for desc read (onDescReadFinished 1).";
760 call->deleteLater();
761 prepareNextJob();
762 return;
763 }
764
765 const QLowEnergyServicePrivate::CharData &charData =
766 service->characteristicList.value(ch.attributeHandle());
767
768 if (!charData.descriptorList.contains(nextJob.handle)) {
769 qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor (onDescReadFinished 2).";
770 call->deleteLater();
771 prepareNextJob();
772 return;
773 }
774
775 bool isServiceDiscovery = nextJob.flags.testFlag(GattJob::ServiceDiscovery);
776 const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid;
777
778 QDBusPendingReply<QByteArray> reply = *call;
779 if (reply.isError()) {
780 qCWarning(QT_BT_BLUEZ) << "Cannot read descriptor (onDescReadFinished 3): "
781 << charData.descriptorList[nextJob.handle].uuid
782 << charData.uuid
783 << reply.error().name() << reply.error().message();
784 if (!isServiceDiscovery)
785 service->setError(QLowEnergyService::DescriptorReadError);
786 } else {
787 qCDebug(QT_BT_BLUEZ) << "Read Desc:" << reply.value();
788 updateValueOfDescriptor(ch.attributeHandle(), nextJob.handle, reply.value(), false);
789
790 if (isServiceDiscovery) {
791 if (nextJob.flags.testFlag(GattJob::LastServiceDiscovery))
792 service->setState(QLowEnergyService::ServiceDiscovered);
793 } else {
794 QLowEnergyDescriptor desc(service, ch.attributeHandle(), nextJob.handle);
795 emit service->descriptorRead(desc, reply.value());
796 }
797 }
798
799 call->deleteLater();
800 prepareNextJob();
801}
802
803void QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished(QDBusPendingCallWatcher *call)
804{
805 if (!jobPending || jobs.isEmpty()) {
806 // this may happen when service disconnects before dbus watcher returns later on
807 qCWarning(QT_BT_BLUEZ) << "Aborting onCharWriteFinished due to disconnect";
808 Q_ASSERT(state == QLowEnergyController::UnconnectedState);
809 return;
810 }
811
812 const GattJob nextJob = jobs.constFirst();
813 Q_ASSERT(nextJob.flags.testFlag(GattJob::CharWrite));
814
815 QSharedPointer<QLowEnergyServicePrivate> service = nextJob.service;
816 if (!dbusServices.contains(service->uuid)) {
817 qCWarning(QT_BT_BLUEZ) << "onCharWriteFinished: Invalid GATT job. Skipping.";
818 call->deleteLater();
819 prepareNextJob();
820 return;
821 }
822
823 const QLowEnergyServicePrivate::CharData &charData =
824 service->characteristicList.value(nextJob.handle);
825
826 QDBusPendingReply<> reply = *call;
827 if (reply.isError()) {
828 qCWarning(QT_BT_BLUEZ) << "Cannot initiate writing of" << charData.uuid
829 << "of service" << service->uuid
830 << reply.error().name() << reply.error().message();
831 service->setError(QLowEnergyService::CharacteristicWriteError);
832 } else {
833 if (charData.properties.testFlag(QLowEnergyCharacteristic::Read))
834 updateValueOfCharacteristic(nextJob.handle, nextJob.value, false);
835
836 QLowEnergyCharacteristic ch(service, nextJob.handle);
837 // write without respone implies zero feedback
838 if (nextJob.writeMode == QLowEnergyService::WriteWithResponse) {
839 qCDebug(QT_BT_BLUEZ) << "Written Char:" << charData.uuid << nextJob.value.toHex();
840 emit service->characteristicWritten(ch, nextJob.value);
841 }
842 }
843
844 call->deleteLater();
845 prepareNextJob();
846}
847
848void QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished(QDBusPendingCallWatcher *call)
849{
850 if (!jobPending || jobs.isEmpty()) {
851 // this may happen when service disconnects before dbus watcher returns later on
852 qCWarning(QT_BT_BLUEZ) << "Aborting onDescWriteFinished due to disconnect";
853 Q_ASSERT(state == QLowEnergyController::UnconnectedState);
854 return;
855 }
856
857 const GattJob nextJob = jobs.constFirst();
858 Q_ASSERT(nextJob.flags.testFlag(GattJob::DescWrite));
859
860 QSharedPointer<QLowEnergyServicePrivate> service = nextJob.service;
861 if (!dbusServices.contains(service->uuid)) {
862 qCWarning(QT_BT_BLUEZ) << "onDescWriteFinished: Invalid GATT job. Skipping.";
863 call->deleteLater();
864 prepareNextJob();
865 return;
866 }
867
868 const QLowEnergyCharacteristic associatedChar = characteristicForHandle(nextJob.handle);
869 const QLowEnergyDescriptor descriptor = descriptorForHandle(nextJob.handle);
870 if (!associatedChar.isValid() || !descriptor.isValid()) {
871 qCWarning(QT_BT_BLUEZ) << "onDescWriteFinished: Cannot find associated char/desc: "
872 << associatedChar.isValid();
873 call->deleteLater();
874 prepareNextJob();
875 return;
876 }
877
878 QDBusPendingReply<> reply = *call;
879 if (reply.isError()) {
880 qCWarning(QT_BT_BLUEZ) << "Cannot initiate writing of" << descriptor.uuid()
881 << "of char" << associatedChar.uuid()
882 << "of service" << service->uuid
883 << reply.error().name() << reply.error().message();
884 service->setError(QLowEnergyService::DescriptorWriteError);
885 } else {
886 qCDebug(QT_BT_BLUEZ) << "Write Desc:" << descriptor.uuid() << nextJob.value.toHex();
887 updateValueOfDescriptor(associatedChar.attributeHandle(), nextJob.handle,
888 nextJob.value, false);
889 emit service->descriptorWritten(descriptor, nextJob.value);
890 }
891
892 call->deleteLater();
893 prepareNextJob();
894}
895
896void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob()
897{
898 if (jobPending || jobs.isEmpty())
899 return;
900
901 jobPending = true;
902
903 const GattJob nextJob = jobs.constFirst();
904 QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(nextJob.handle);
905 if (service.isNull() || !dbusServices.contains(service->uuid)) {
906 qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleNextJob). Skipping.";
907 prepareNextJob();
908 return;
909 }
910
911 const GattService &dbusServiceData = dbusServices[service->uuid];
912
913 if (nextJob.flags.testFlag(GattJob::CharRead)) {
914 // characteristic reading ***************************************
915 if (!service->characteristicList.contains(nextJob.handle)) {
916 qCWarning(QT_BT_BLUEZ) << "Invalid Char handle when reading. Skipping.";
917 prepareNextJob();
918 return;
919 }
920
921 const QLowEnergyServicePrivate::CharData &charData =
922 service->characteristicList.value(nextJob.handle);
923 bool foundChar = false;
924 for (const auto &gattChar : qAsConst(dbusServiceData.characteristics)) {
925 if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID()))
926 continue;
927
928 QDBusPendingReply<QByteArray> reply = gattChar.characteristic->ReadValue(QVariantMap());
929 QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
930 connect(watcher, &QDBusPendingCallWatcher::finished,
931 this, &QLowEnergyControllerPrivateBluezDBus::onCharReadFinished);
932
933 foundChar = true;
934 break;
935 }
936
937 if (!foundChar) {
938 qCWarning(QT_BT_BLUEZ) << "Cannot find char for reading. Skipping.";
939 prepareNextJob();
940 return;
941 }
942 } else if (nextJob.flags.testFlag(GattJob::CharWrite)) {
943 // characteristic writing ***************************************
944 if (!service->characteristicList.contains(nextJob.handle)) {
945 qCWarning(QT_BT_BLUEZ) << "Invalid Char handle when writing. Skipping.";
946 prepareNextJob();
947 return;
948 }
949
950 const QLowEnergyServicePrivate::CharData &charData =
951 service->characteristicList.value(nextJob.handle);
952 bool foundChar = false;
953 for (const auto &gattChar : qAsConst(dbusServiceData.characteristics)) {
954 if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID()))
955 continue;
956
957 QDBusPendingReply<> reply = gattChar.characteristic->WriteValue(nextJob.value, QVariantMap());
958 QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
959 connect(watcher, &QDBusPendingCallWatcher::finished,
960 this, &QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished);
961
962 foundChar = true;
963 break;
964 }
965
966 if (!foundChar) {
967 qCWarning(QT_BT_BLUEZ) << "Cannot find char for writing. Skipping.";
968 prepareNextJob();
969 return;
970 }
971 } else if (nextJob.flags.testFlag(GattJob::DescRead)) {
972 // descriptor reading ***************************************
973 QLowEnergyCharacteristic ch = characteristicForHandle(nextJob.handle);
974 if (!ch.isValid()) {
975 qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleReadDesc 1). Skipping.";
976 prepareNextJob();
977 return;
978 }
979
980 const QLowEnergyServicePrivate::CharData &charData =
981 service->characteristicList.value(ch.attributeHandle());
982 if (!charData.descriptorList.contains(nextJob.handle)) {
983 qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleReadDesc 2). Skipping.";
984 prepareNextJob();
985 return;
986 }
987
988 const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid;
989 bool foundDesc = false;
990 for (const auto &gattChar : qAsConst(dbusServiceData.characteristics)) {
991 if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID()))
992 continue;
993
994 for (const auto &gattDesc : qAsConst(gattChar.descriptors)) {
995 if (descUuid != QBluetoothUuid(gattDesc->uUID()))
996 continue;
997
998 QDBusPendingReply<QByteArray> reply = gattDesc->ReadValue(QVariantMap());
999 QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
1000 connect(watcher, &QDBusPendingCallWatcher::finished,
1001 this, &QLowEnergyControllerPrivateBluezDBus::onDescReadFinished);
1002 foundDesc = true;
1003 break;
1004 }
1005
1006 if (foundDesc)
1007 break;
1008 }
1009
1010 if (!foundDesc) {
1011 qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor for reading. Skipping.";
1012 prepareNextJob();
1013 return;
1014 }
1015 } else if (nextJob.flags.testFlag(GattJob::DescWrite)) {
1016 // descriptor writing ***************************************
1017 const QLowEnergyCharacteristic ch = characteristicForHandle(nextJob.handle);
1018 if (!ch.isValid()) {
1019 qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleWriteDesc 1). Skipping.";
1020 prepareNextJob();
1021 return;
1022 }
1023
1024 const QLowEnergyServicePrivate::CharData &charData =
1025 service->characteristicList.value(ch.attributeHandle());
1026 if (!charData.descriptorList.contains(nextJob.handle)) {
1027 qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleWriteDesc 2). Skipping.";
1028 prepareNextJob();
1029 return;
1030 }
1031
1032 const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid;
1033 bool foundDesc = false;
1034 for (const auto &gattChar : qAsConst(dbusServiceData.characteristics)) {
1035 if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID()))
1036 continue;
1037
1038 for (const auto &gattDesc : qAsConst(gattChar.descriptors)) {
1039 if (descUuid != QBluetoothUuid(gattDesc->uUID()))
1040 continue;
1041
1042 //notifications enabled via characteristics Start/StopNotify() functions
1043 //otherwise regular WriteValue() calls on descriptor interface
1044 if (descUuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) {
1045 const QByteArray value = nextJob.value;
1046
1047 QDBusPendingReply<> reply;
1048 qCDebug(QT_BT_BLUEZ) << "Init CCC change to" << value.toHex()
1049 << charData.uuid << service->uuid;
1050 if (value == QByteArray::fromHex("0100") || value == QByteArray::fromHex("0200"))
1051 reply = gattChar.characteristic->StartNotify();
1052 else
1053 reply = gattChar.characteristic->StopNotify();
1054 QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
1055 connect(watcher, &QDBusPendingCallWatcher::finished,
1056 this, &QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished);
1057 } else {
1058 QDBusPendingReply<> reply = gattDesc->WriteValue(nextJob.value, QVariantMap());
1059 QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
1060 connect(watcher, &QDBusPendingCallWatcher::finished,
1061 this, &QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished);
1062
1063 }
1064
1065 foundDesc = true;
1066 break;
1067 }
1068
1069 if (foundDesc)
1070 break;
1071 }
1072
1073 if (!foundDesc) {
1074 qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor for writing. Skipping.";
1075 prepareNextJob();
1076 return;
1077 }
1078 } else {
1079 qCWarning(QT_BT_BLUEZ) << "Unknown gatt job type. Skipping.";
1080 prepareNextJob();
1081 }
1082}
1083
1084void QLowEnergyControllerPrivateBluezDBus::readCharacteristic(
1085 const QSharedPointer<QLowEnergyServicePrivate> service,
1086 const QLowEnergyHandle charHandle)
1087{
1088 Q_ASSERT(!service.isNull());
1089 if (!service->characteristicList.contains(charHandle)) {
1090 qCWarning(QT_BT_BLUEZ) << "Read characteristic does not belong to service"
1091 << service->uuid;
1092 return;
1093 }
1094
1095 const QLowEnergyServicePrivate::CharData &charDetails
1096 = service->characteristicList[charHandle];
1097 if (!(charDetails.properties & QLowEnergyCharacteristic::Read)) {
1098 // if this succeeds the device has a bug, char is advertised as
1099 // non-readable. We try to be permissive and let the remote
1100 // device answer to the read attempt
1101 qCWarning(QT_BT_BLUEZ) << "Reading non-readable char" << charHandle;
1102 }
1103
1104 const GattService &gattService = dbusServices[service->uuid];
1105 if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) {
1106 // Reread from dbus interface and write to local cache
1107 const QByteArray newValue(1, char(gattService.batteryInterface->percentage()));
1108 quint16 result = updateValueOfCharacteristic(charHandle, newValue, false);
1109 if (result > 0) {
1110 QLowEnergyCharacteristic ch(service, charHandle);
1111 emit service->characteristicRead(ch, newValue);
1112 } else {
1113 service->setError(QLowEnergyService::CharacteristicReadError);
1114 }
1115 return;
1116 }
1117
1118 GattJob job;
1119 job.flags = GattJob::JobFlags({GattJob::CharRead});
1120 job.service = service;
1121 job.handle = charHandle;
1122 jobs.append(job);
1123
1124 scheduleNextJob();
1125}
1126
1127void QLowEnergyControllerPrivateBluezDBus::readDescriptor(
1128 const QSharedPointer<QLowEnergyServicePrivate> service,
1129 const QLowEnergyHandle charHandle,
1130 const QLowEnergyHandle descriptorHandle)
1131{
1132 Q_ASSERT(!service.isNull());
1133 if (!service->characteristicList.contains(charHandle))
1134 return;
1135
1136 const QLowEnergyServicePrivate::CharData &charDetails
1137 = service->characteristicList[charHandle];
1138 if (!charDetails.descriptorList.contains(descriptorHandle))
1139 return;
1140
1141 const GattService &gattService = dbusServices[service->uuid];
1142 if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) {
1143 auto descriptor = descriptorForHandle(descriptorHandle);
1144 if (descriptor.isValid())
1145 emit service->descriptorRead(descriptor, descriptor.value());
1146 else
1147 service->setError(QLowEnergyService::DescriptorReadError);
1148
1149 return;
1150 }
1151
1152 GattJob job;
1153 job.flags = GattJob::JobFlags({GattJob::DescRead});
1154 job.service = service;
1155 job.handle = descriptorHandle;
1156 jobs.append(job);
1157
1158 scheduleNextJob();
1159}
1160
1161void QLowEnergyControllerPrivateBluezDBus::writeCharacteristic(
1162 const QSharedPointer<QLowEnergyServicePrivate> service,
1163 const QLowEnergyHandle charHandle,
1164 const QByteArray &newValue,
1165 QLowEnergyService::WriteMode writeMode)
1166{
1167 Q_ASSERT(!service.isNull());
1168 if (!service->characteristicList.contains(charHandle)) {
1169 qCWarning(QT_BT_BLUEZ) << "Write characteristic does not belong to service"
1170 << service->uuid;
1171 return;
1172 }
1173
1174 if (role == QLowEnergyController::CentralRole) {
1175 const GattService &gattService = dbusServices[service->uuid];
1176 if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) {
1177 //Battery1 interface is readonly
1178 service->setError(QLowEnergyService::CharacteristicWriteError);
1179 return;
1180 }
1181
1182
1183 GattJob job;
1184 job.flags = GattJob::JobFlags({GattJob::CharWrite});
1185 job.service = service;
1186 job.handle = charHandle;
1187 job.value = newValue;
1188 job.writeMode = writeMode;
1189 jobs.append(job);
1190
1191 scheduleNextJob();
1192 } else {
1193 qWarning(QT_BT_BLUEZ) << "writeCharacteristic() not implemented for DBus Bluez GATT";
1194 service->setError(QLowEnergyService::CharacteristicWriteError);
1195 }
1196}
1197
1198void QLowEnergyControllerPrivateBluezDBus::writeDescriptor(
1199 const QSharedPointer<QLowEnergyServicePrivate> service,
1200 const QLowEnergyHandle charHandle,
1201 const QLowEnergyHandle descriptorHandle,
1202 const QByteArray &newValue)
1203{
1204 Q_ASSERT(!service.isNull());
1205 if (!service->characteristicList.contains(charHandle))
1206 return;
1207
1208 if (role == QLowEnergyController::CentralRole) {
1209 const GattService &gattService = dbusServices[service->uuid];
1210 if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) {
1211 auto descriptor = descriptorForHandle(descriptorHandle);
1212 if (!descriptor.isValid())
1213 return;
1214
1215 if (descriptor.uuid() == QBluetoothUuid::ClientCharacteristicConfiguration) {
1216 if (newValue == QByteArray::fromHex("0000")
1217 || newValue == QByteArray::fromHex("0100")
1218 || newValue == QByteArray::fromHex("0200")) {
1219 quint16 result = updateValueOfDescriptor(charHandle, descriptorHandle, newValue, false);
1220 if (result > 0)
1221 emit service->descriptorWritten(descriptor, newValue);
1222 else
1223 emit service->setError(QLowEnergyService::DescriptorWriteError);
1224
1225 }
1226 } else {
1227 service->setError(QLowEnergyService::DescriptorWriteError);
1228 }
1229
1230 return;
1231 }
1232
1233 GattJob job;
1234 job.flags = GattJob::JobFlags({GattJob::DescWrite});
1235 job.service = service;
1236 job.handle = descriptorHandle;
1237 job.value = newValue;
1238 jobs.append(job);
1239
1240 scheduleNextJob();
1241 } else {
1242 qWarning(QT_BT_BLUEZ) << "writeDescriptor() peripheral not implemented for DBus Bluez GATT";
1243 service->setError(QLowEnergyService::CharacteristicWriteError);
1244 }
1245}
1246
1247void QLowEnergyControllerPrivateBluezDBus::startAdvertising(
1248 const QLowEnergyAdvertisingParameters &/* params */,
1249 const QLowEnergyAdvertisingData &/* advertisingData */,
1250 const QLowEnergyAdvertisingData &/* scanResponseData */)
1251{
1252}
1253
1254void QLowEnergyControllerPrivateBluezDBus::stopAdvertising()
1255{
1256}
1257
1258void QLowEnergyControllerPrivateBluezDBus::requestConnectionUpdate(
1259 const QLowEnergyConnectionParameters & /* params */)
1260{
1261}
1262
1263void QLowEnergyControllerPrivateBluezDBus::addToGenericAttributeList(
1264 const QLowEnergyServiceData &/* service */,
1265 QLowEnergyHandle /* startHandle */)
1266{
1267}
1268
1269QLowEnergyService *QLowEnergyControllerPrivateBluezDBus::addServiceHelper(
1270 const QLowEnergyServiceData &/*service*/)
1271{
1272 return nullptr;
1273}
1274
1275QT_END_NAMESPACE
1276