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 | |
52 | QT_BEGIN_NAMESPACE |
53 | |
54 | Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) |
55 | |
56 | QLowEnergyControllerPrivateBluezDBus::QLowEnergyControllerPrivateBluezDBus() |
57 | : QLowEnergyControllerPrivate() |
58 | { |
59 | } |
60 | |
61 | QLowEnergyControllerPrivateBluezDBus::~QLowEnergyControllerPrivateBluezDBus() |
62 | { |
63 | } |
64 | |
65 | void QLowEnergyControllerPrivateBluezDBus::init() |
66 | { |
67 | } |
68 | |
69 | void 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 | |
165 | void 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 | |
194 | void 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 | |
206 | void 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 | |
236 | void 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 | |
309 | void 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 | |
355 | void 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 | |
377 | void 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 | |
456 | void 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 | |
507 | void 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 | |
522 | void 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 | |
681 | void QLowEnergyControllerPrivateBluezDBus::prepareNextJob() |
682 | { |
683 | jobs.takeFirst(); // finish last job |
684 | jobPending = false; |
685 | |
686 | scheduleNextJob(); // continue with next job - if available |
687 | } |
688 | |
689 | void 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 | |
737 | void 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 | |
803 | void 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 | |
848 | void 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 | |
896 | void 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 | |
1084 | void 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 | |
1127 | void 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 | |
1161 | void 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 | |
1198 | void 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 | |
1247 | void QLowEnergyControllerPrivateBluezDBus::startAdvertising( |
1248 | const QLowEnergyAdvertisingParameters &/* params */, |
1249 | const QLowEnergyAdvertisingData &/* advertisingData */, |
1250 | const QLowEnergyAdvertisingData &/* scanResponseData */) |
1251 | { |
1252 | } |
1253 | |
1254 | void QLowEnergyControllerPrivateBluezDBus::stopAdvertising() |
1255 | { |
1256 | } |
1257 | |
1258 | void QLowEnergyControllerPrivateBluezDBus::requestConnectionUpdate( |
1259 | const QLowEnergyConnectionParameters & /* params */) |
1260 | { |
1261 | } |
1262 | |
1263 | void QLowEnergyControllerPrivateBluezDBus::addToGenericAttributeList( |
1264 | const QLowEnergyServiceData &/* service */, |
1265 | QLowEnergyHandle /* startHandle */) |
1266 | { |
1267 | } |
1268 | |
1269 | QLowEnergyService *QLowEnergyControllerPrivateBluezDBus::addServiceHelper( |
1270 | const QLowEnergyServiceData &/*service*/) |
1271 | { |
1272 | return nullptr; |
1273 | } |
1274 | |
1275 | QT_END_NAMESPACE |
1276 | |