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