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 OrgFreedesktopDBusPropertiesInterface( |
344 | QStringLiteral("org.bluez" ), devicePath, |
345 | QDBusConnection::systemBus(), this); |
346 | connect(sender: deviceMonitor, signal: &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, |
347 | context: this, slot: &QLowEnergyControllerPrivateBluezDBus::devicePropertiesChanged); |
348 | } |
349 | |
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 | QStringList descriptorPaths; |
648 | const ManagedObjectList managedObjectList = reply.value(); |
649 | for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) { |
650 | const InterfaceList &ifaceList = it.value(); |
651 | if (!it.key().path().startsWith(s: dbusData.servicePath)) |
652 | continue; |
653 | |
654 | for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) { |
655 | const QString &iface = jt.key(); |
656 | if (iface == QStringLiteral("org.bluez.GattCharacteristic1" )) { |
657 | auto charInterface = QSharedPointer<OrgBluezGattCharacteristic1Interface>::create( |
658 | QStringLiteral("org.bluez" ), arguments: it.key().path(), |
659 | arguments: QDBusConnection::systemBus()); |
660 | GattCharacteristic dbusCharData; |
661 | dbusCharData.characteristic = charInterface; |
662 | dbusData.characteristics.append(t: dbusCharData); |
663 | } else if (iface == QStringLiteral("org.bluez.GattDescriptor1" )) { |
664 | auto descInterface = QSharedPointer<OrgBluezGattDescriptor1Interface>::create( |
665 | QStringLiteral("org.bluez" ), arguments: it.key().path(), |
666 | arguments: QDBusConnection::systemBus()); |
667 | bool found = false; |
668 | for (GattCharacteristic &dbusCharData : dbusData.characteristics) { |
669 | if (!descInterface->path().startsWith( |
670 | s: dbusCharData.characteristic->path())) |
671 | continue; |
672 | |
673 | found = true; |
674 | dbusCharData.descriptors.append(t: descInterface); |
675 | break; |
676 | } |
677 | |
678 | Q_ASSERT(found); |
679 | if (!found) |
680 | qCWarning(QT_BT_BLUEZ) << "Descriptor discovery error" ; |
681 | } |
682 | } |
683 | } |
684 | |
685 | //populate servicePrivate based on dbus data |
686 | serviceData->startHandle = runningHandle++; |
687 | for (GattCharacteristic &dbusChar : dbusData.characteristics) { |
688 | const QLowEnergyHandle indexHandle = runningHandle++; |
689 | QLowEnergyServicePrivate::CharData charData; |
690 | |
691 | // characteristic data |
692 | charData.valueHandle = runningHandle++; |
693 | const QStringList properties = dbusChar.characteristic->flags(); |
694 | |
695 | for (const auto &entry : properties) { |
696 | if (entry == QStringLiteral("broadcast" )) |
697 | charData.properties.setFlag(flag: QLowEnergyCharacteristic::Broadcasting, on: true); |
698 | else if (entry == QStringLiteral("read" )) |
699 | charData.properties.setFlag(flag: QLowEnergyCharacteristic::Read, on: true); |
700 | else if (entry == QStringLiteral("write-without-response" )) |
701 | charData.properties.setFlag(flag: QLowEnergyCharacteristic::WriteNoResponse, on: true); |
702 | else if (entry == QStringLiteral("write" )) |
703 | charData.properties.setFlag(flag: QLowEnergyCharacteristic::Write, on: true); |
704 | else if (entry == QStringLiteral("notify" )) |
705 | charData.properties.setFlag(flag: QLowEnergyCharacteristic::Notify, on: true); |
706 | else if (entry == QStringLiteral("indicate" )) |
707 | charData.properties.setFlag(flag: QLowEnergyCharacteristic::Indicate, on: true); |
708 | else if (entry == QStringLiteral("authenticated-signed-writes" )) |
709 | charData.properties.setFlag(flag: QLowEnergyCharacteristic::WriteSigned, on: true); |
710 | else if (entry == QStringLiteral("reliable-write" )) |
711 | charData.properties.setFlag(flag: QLowEnergyCharacteristic::ExtendedProperty, on: true); |
712 | else if (entry == QStringLiteral("writable-auxiliaries" )) |
713 | charData.properties.setFlag(flag: QLowEnergyCharacteristic::ExtendedProperty, on: true); |
714 | //all others ignored - not relevant for this API |
715 | } |
716 | |
717 | charData.uuid = QBluetoothUuid(dbusChar.characteristic->uUID()); |
718 | |
719 | // schedule read for initial char value |
720 | if (mode == QLowEnergyService::FullDiscovery |
721 | && charData.properties.testFlag(flag: QLowEnergyCharacteristic::Read)) { |
722 | GattJob job; |
723 | job.flags = GattJob::JobFlags({GattJob::CharRead, GattJob::ServiceDiscovery}); |
724 | job.service = serviceData; |
725 | job.handle = indexHandle; |
726 | jobs.append(t: job); |
727 | } |
728 | |
729 | // descriptor data |
730 | for (const auto &descEntry : std::as_const(t&: dbusChar.descriptors)) { |
731 | const QLowEnergyHandle descriptorHandle = runningHandle++; |
732 | QLowEnergyServicePrivate::DescData descData; |
733 | descData.uuid = QBluetoothUuid(descEntry->uUID()); |
734 | charData.descriptorList.insert(key: descriptorHandle, value: descData); |
735 | |
736 | |
737 | // every ClientCharacteristicConfiguration needs to track property changes |
738 | if (descData.uuid |
739 | == QBluetoothUuid(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration)) { |
740 | dbusChar.charMonitor = QSharedPointer<OrgFreedesktopDBusPropertiesInterface>::create( |
741 | QStringLiteral("org.bluez" ), |
742 | arguments: dbusChar.characteristic->path(), |
743 | arguments: QDBusConnection::systemBus(), arguments: this); |
744 | connect(sender: dbusChar.charMonitor.data(), signal: &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, |
745 | context: this, slot: [this, indexHandle](const QString &interface, const QVariantMap &changedProperties, |
746 | const QStringList &removedProperties) { |
747 | |
748 | characteristicPropertiesChanged(charHandle: indexHandle, interface, |
749 | changedProperties, removedProperties); |
750 | }); |
751 | } |
752 | |
753 | if (mode == QLowEnergyService::FullDiscovery) { |
754 | // schedule read for initial descriptor value |
755 | GattJob job; |
756 | job.flags = GattJob::JobFlags({ GattJob::DescRead, GattJob::ServiceDiscovery }); |
757 | job.service = serviceData; |
758 | job.handle = descriptorHandle; |
759 | jobs.append(t: job); |
760 | } |
761 | } |
762 | |
763 | serviceData->characteristicList[indexHandle] = charData; |
764 | } |
765 | |
766 | serviceData->endHandle = runningHandle++; |
767 | |
768 | // last job is last step of service discovery |
769 | if (!jobs.isEmpty()) { |
770 | GattJob &lastJob = jobs.last(); |
771 | lastJob.flags.setFlag(flag: GattJob::LastServiceDiscovery, on: true); |
772 | } else { |
773 | serviceData->setState(QLowEnergyService::RemoteServiceDiscovered); |
774 | } |
775 | |
776 | scheduleNextJob(); |
777 | } |
778 | |
779 | void QLowEnergyControllerPrivateBluezDBus::prepareNextJob() |
780 | { |
781 | jobs.takeFirst(); // finish last job |
782 | jobPending = false; |
783 | |
784 | scheduleNextJob(); // continue with next job - if available |
785 | } |
786 | |
787 | void QLowEnergyControllerPrivateBluezDBus::onCharReadFinished(QDBusPendingCallWatcher *call) |
788 | { |
789 | if (!jobPending || jobs.isEmpty()) { |
790 | // this may happen when service disconnects before dbus watcher returns later on |
791 | qCWarning(QT_BT_BLUEZ) << "Aborting onCharReadFinished due to disconnect" ; |
792 | Q_ASSERT(state == QLowEnergyController::UnconnectedState); |
793 | return; |
794 | } |
795 | |
796 | const GattJob nextJob = jobs.constFirst(); |
797 | Q_ASSERT(nextJob.flags.testFlag(GattJob::CharRead)); |
798 | |
799 | QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(handle: nextJob.handle); |
800 | if (service.isNull() || !dbusServices.contains(key: service->uuid)) { |
801 | qCWarning(QT_BT_BLUEZ) << "onCharReadFinished: Invalid GATT job. Skipping." ; |
802 | call->deleteLater(); |
803 | prepareNextJob(); |
804 | return; |
805 | } |
806 | const QLowEnergyServicePrivate::CharData &charData = |
807 | service->characteristicList.value(key: nextJob.handle); |
808 | |
809 | bool isServiceDiscovery = nextJob.flags.testFlag(flag: GattJob::ServiceDiscovery); |
810 | QDBusPendingReply<QByteArray> reply = *call; |
811 | if (reply.isError()) { |
812 | qCWarning(QT_BT_BLUEZ) << "Cannot initiate reading of" << charData.uuid |
813 | << "of service" << service->uuid |
814 | << reply.error().name() << reply.error().message(); |
815 | if (!isServiceDiscovery) |
816 | service->setError(QLowEnergyService::CharacteristicReadError); |
817 | } else { |
818 | qCDebug(QT_BT_BLUEZ) << "Read Char:" << charData.uuid << reply.value().toHex(); |
819 | if (charData.properties.testFlag(flag: QLowEnergyCharacteristic::Read)) |
820 | updateValueOfCharacteristic(charHandle: nextJob.handle, value: reply.value(), appendValue: false); |
821 | |
822 | if (isServiceDiscovery) { |
823 | if (nextJob.flags.testFlag(flag: GattJob::LastServiceDiscovery)) |
824 | service->setState(QLowEnergyService::RemoteServiceDiscovered); |
825 | } else { |
826 | QLowEnergyCharacteristic ch(service, nextJob.handle); |
827 | emit service->characteristicRead(info: ch, value: reply.value()); |
828 | } |
829 | } |
830 | |
831 | call->deleteLater(); |
832 | prepareNextJob(); |
833 | } |
834 | |
835 | void QLowEnergyControllerPrivateBluezDBus::onDescReadFinished(QDBusPendingCallWatcher *call) |
836 | { |
837 | if (!jobPending || jobs.isEmpty()) { |
838 | // this may happen when service disconnects before dbus watcher returns later on |
839 | qCWarning(QT_BT_BLUEZ) << "Aborting onDescReadFinished due to disconnect" ; |
840 | Q_ASSERT(state == QLowEnergyController::UnconnectedState); |
841 | return; |
842 | } |
843 | |
844 | const GattJob nextJob = jobs.constFirst(); |
845 | Q_ASSERT(nextJob.flags.testFlag(GattJob::DescRead)); |
846 | |
847 | QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(handle: nextJob.handle); |
848 | if (service.isNull() || !dbusServices.contains(key: service->uuid)) { |
849 | qCWarning(QT_BT_BLUEZ) << "onDescReadFinished: Invalid GATT job. Skipping." ; |
850 | call->deleteLater(); |
851 | prepareNextJob(); |
852 | return; |
853 | } |
854 | |
855 | QLowEnergyCharacteristic ch = characteristicForHandle(handle: nextJob.handle); |
856 | if (!ch.isValid()) { |
857 | qCWarning(QT_BT_BLUEZ) << "Cannot find char for desc read (onDescReadFinished 1)." ; |
858 | call->deleteLater(); |
859 | prepareNextJob(); |
860 | return; |
861 | } |
862 | |
863 | const QLowEnergyServicePrivate::CharData &charData = |
864 | service->characteristicList.value(key: ch.attributeHandle()); |
865 | |
866 | if (!charData.descriptorList.contains(key: nextJob.handle)) { |
867 | qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor (onDescReadFinished 2)." ; |
868 | call->deleteLater(); |
869 | prepareNextJob(); |
870 | return; |
871 | } |
872 | |
873 | bool isServiceDiscovery = nextJob.flags.testFlag(flag: GattJob::ServiceDiscovery); |
874 | |
875 | QDBusPendingReply<QByteArray> reply = *call; |
876 | if (reply.isError()) { |
877 | qCWarning(QT_BT_BLUEZ) << "Cannot read descriptor (onDescReadFinished 3): " |
878 | << charData.descriptorList[nextJob.handle].uuid |
879 | << charData.uuid |
880 | << reply.error().name() << reply.error().message(); |
881 | if (!isServiceDiscovery) |
882 | service->setError(QLowEnergyService::DescriptorReadError); |
883 | } else { |
884 | qCDebug(QT_BT_BLUEZ) << "Read Desc:" << reply.value(); |
885 | updateValueOfDescriptor(charHandle: ch.attributeHandle(), descriptorHandle: nextJob.handle, value: reply.value(), appendValue: false); |
886 | |
887 | if (isServiceDiscovery) { |
888 | if (nextJob.flags.testFlag(flag: GattJob::LastServiceDiscovery)) |
889 | service->setState(QLowEnergyService::RemoteServiceDiscovered); |
890 | } else { |
891 | QLowEnergyDescriptor desc(service, ch.attributeHandle(), nextJob.handle); |
892 | emit service->descriptorRead(info: desc, value: reply.value()); |
893 | } |
894 | } |
895 | |
896 | call->deleteLater(); |
897 | prepareNextJob(); |
898 | } |
899 | |
900 | void QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished(QDBusPendingCallWatcher *call) |
901 | { |
902 | if (!jobPending || jobs.isEmpty()) { |
903 | // this may happen when service disconnects before dbus watcher returns later on |
904 | qCWarning(QT_BT_BLUEZ) << "Aborting onCharWriteFinished due to disconnect" ; |
905 | Q_ASSERT(state == QLowEnergyController::UnconnectedState); |
906 | return; |
907 | } |
908 | |
909 | const GattJob nextJob = jobs.constFirst(); |
910 | Q_ASSERT(nextJob.flags.testFlag(GattJob::CharWrite)); |
911 | |
912 | QSharedPointer<QLowEnergyServicePrivate> service = nextJob.service; |
913 | if (!dbusServices.contains(key: service->uuid)) { |
914 | qCWarning(QT_BT_BLUEZ) << "onCharWriteFinished: Invalid GATT job. Skipping." ; |
915 | call->deleteLater(); |
916 | prepareNextJob(); |
917 | return; |
918 | } |
919 | |
920 | const QLowEnergyServicePrivate::CharData &charData = |
921 | service->characteristicList.value(key: nextJob.handle); |
922 | |
923 | QDBusPendingReply<> reply = *call; |
924 | if (reply.isError()) { |
925 | qCWarning(QT_BT_BLUEZ) << "Cannot initiate writing of" << charData.uuid |
926 | << "of service" << service->uuid |
927 | << reply.error().name() << reply.error().message(); |
928 | service->setError(QLowEnergyService::CharacteristicWriteError); |
929 | } else { |
930 | if (charData.properties.testFlag(flag: QLowEnergyCharacteristic::Read)) |
931 | updateValueOfCharacteristic(charHandle: nextJob.handle, value: nextJob.value, appendValue: false); |
932 | |
933 | QLowEnergyCharacteristic ch(service, nextJob.handle); |
934 | // write without response implies zero feedback |
935 | if (nextJob.writeMode == QLowEnergyService::WriteWithResponse) { |
936 | qCDebug(QT_BT_BLUEZ) << "Written Char:" << charData.uuid << nextJob.value.toHex(); |
937 | emit service->characteristicWritten(characteristic: ch, newValue: nextJob.value); |
938 | } |
939 | } |
940 | |
941 | call->deleteLater(); |
942 | prepareNextJob(); |
943 | } |
944 | |
945 | void QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished(QDBusPendingCallWatcher *call) |
946 | { |
947 | if (!jobPending || jobs.isEmpty()) { |
948 | // this may happen when service disconnects before dbus watcher returns later on |
949 | qCWarning(QT_BT_BLUEZ) << "Aborting onDescWriteFinished due to disconnect" ; |
950 | Q_ASSERT(state == QLowEnergyController::UnconnectedState); |
951 | return; |
952 | } |
953 | |
954 | const GattJob nextJob = jobs.constFirst(); |
955 | Q_ASSERT(nextJob.flags.testFlag(GattJob::DescWrite)); |
956 | |
957 | QSharedPointer<QLowEnergyServicePrivate> service = nextJob.service; |
958 | if (!dbusServices.contains(key: service->uuid)) { |
959 | qCWarning(QT_BT_BLUEZ) << "onDescWriteFinished: Invalid GATT job. Skipping." ; |
960 | call->deleteLater(); |
961 | prepareNextJob(); |
962 | return; |
963 | } |
964 | |
965 | const QLowEnergyCharacteristic associatedChar = characteristicForHandle(handle: nextJob.handle); |
966 | const QLowEnergyDescriptor descriptor = descriptorForHandle(handle: nextJob.handle); |
967 | if (!associatedChar.isValid() || !descriptor.isValid()) { |
968 | qCWarning(QT_BT_BLUEZ) << "onDescWriteFinished: Cannot find associated char/desc: " |
969 | << associatedChar.isValid(); |
970 | call->deleteLater(); |
971 | prepareNextJob(); |
972 | return; |
973 | } |
974 | |
975 | QDBusPendingReply<> reply = *call; |
976 | if (reply.isError()) { |
977 | qCWarning(QT_BT_BLUEZ) << "Cannot initiate writing of" << descriptor.uuid() |
978 | << "of char" << associatedChar.uuid() |
979 | << "of service" << service->uuid |
980 | << reply.error().name() << reply.error().message(); |
981 | service->setError(QLowEnergyService::DescriptorWriteError); |
982 | } else { |
983 | qCDebug(QT_BT_BLUEZ) << "Write Desc:" << descriptor.uuid() << nextJob.value.toHex(); |
984 | updateValueOfDescriptor(charHandle: associatedChar.attributeHandle(), descriptorHandle: nextJob.handle, |
985 | value: nextJob.value, appendValue: false); |
986 | emit service->descriptorWritten(descriptor, newValue: nextJob.value); |
987 | } |
988 | |
989 | call->deleteLater(); |
990 | prepareNextJob(); |
991 | } |
992 | |
993 | void QLowEnergyControllerPrivateBluezDBus::scheduleNextJob() |
994 | { |
995 | if (jobPending || jobs.isEmpty()) |
996 | return; |
997 | |
998 | jobPending = true; |
999 | |
1000 | const GattJob nextJob = jobs.constFirst(); |
1001 | QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(handle: nextJob.handle); |
1002 | if (service.isNull() || !dbusServices.contains(key: service->uuid)) { |
1003 | qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleNextJob). Skipping." ; |
1004 | prepareNextJob(); |
1005 | return; |
1006 | } |
1007 | |
1008 | const GattService &dbusServiceData = dbusServices[service->uuid]; |
1009 | |
1010 | if (nextJob.flags.testFlag(flag: GattJob::CharRead)) { |
1011 | // characteristic reading *************************************** |
1012 | if (!service->characteristicList.contains(key: nextJob.handle)) { |
1013 | qCWarning(QT_BT_BLUEZ) << "Invalid Char handle when reading. Skipping." ; |
1014 | prepareNextJob(); |
1015 | return; |
1016 | } |
1017 | |
1018 | const QLowEnergyServicePrivate::CharData &charData = |
1019 | service->characteristicList.value(key: nextJob.handle); |
1020 | bool foundChar = false; |
1021 | for (const auto &gattChar : std::as_const(t: dbusServiceData.characteristics)) { |
1022 | if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID())) |
1023 | continue; |
1024 | |
1025 | QDBusPendingReply<QByteArray> reply = gattChar.characteristic->ReadValue(options: QVariantMap()); |
1026 | QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); |
1027 | connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, |
1028 | context: this, slot: &QLowEnergyControllerPrivateBluezDBus::onCharReadFinished); |
1029 | |
1030 | foundChar = true; |
1031 | break; |
1032 | } |
1033 | |
1034 | if (!foundChar) { |
1035 | qCWarning(QT_BT_BLUEZ) << "Cannot find char for reading. Skipping." ; |
1036 | prepareNextJob(); |
1037 | return; |
1038 | } |
1039 | } else if (nextJob.flags.testFlag(flag: GattJob::CharWrite)) { |
1040 | // characteristic writing *************************************** |
1041 | if (!service->characteristicList.contains(key: nextJob.handle)) { |
1042 | qCWarning(QT_BT_BLUEZ) << "Invalid Char handle when writing. Skipping." ; |
1043 | prepareNextJob(); |
1044 | return; |
1045 | } |
1046 | |
1047 | const QLowEnergyServicePrivate::CharData &charData = |
1048 | service->characteristicList.value(key: nextJob.handle); |
1049 | bool foundChar = false; |
1050 | for (const auto &gattChar : std::as_const(t: dbusServiceData.characteristics)) { |
1051 | if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID())) |
1052 | continue; |
1053 | |
1054 | QVariantMap options; |
1055 | // The "type" option only works with BlueZ >= 5.50, older versions always write with response |
1056 | options[QStringLiteral("type" )] = nextJob.writeMode == QLowEnergyService::WriteWithoutResponse ? |
1057 | QStringLiteral("command" ) : QStringLiteral("request" ); |
1058 | QDBusPendingReply<> reply = gattChar.characteristic->WriteValue(value: nextJob.value, options); |
1059 | |
1060 | QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); |
1061 | connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, |
1062 | context: this, slot: &QLowEnergyControllerPrivateBluezDBus::onCharWriteFinished); |
1063 | |
1064 | foundChar = true; |
1065 | break; |
1066 | } |
1067 | |
1068 | if (!foundChar) { |
1069 | qCWarning(QT_BT_BLUEZ) << "Cannot find char for writing. Skipping." ; |
1070 | prepareNextJob(); |
1071 | return; |
1072 | } |
1073 | } else if (nextJob.flags.testFlag(flag: GattJob::DescRead)) { |
1074 | // descriptor reading *************************************** |
1075 | QLowEnergyCharacteristic ch = characteristicForHandle(handle: nextJob.handle); |
1076 | if (!ch.isValid()) { |
1077 | qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleReadDesc 1). Skipping." ; |
1078 | prepareNextJob(); |
1079 | return; |
1080 | } |
1081 | |
1082 | const QLowEnergyServicePrivate::CharData &charData = |
1083 | service->characteristicList.value(key: ch.attributeHandle()); |
1084 | if (!charData.descriptorList.contains(key: nextJob.handle)) { |
1085 | qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleReadDesc 2). Skipping." ; |
1086 | prepareNextJob(); |
1087 | return; |
1088 | } |
1089 | |
1090 | const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid; |
1091 | bool foundDesc = false; |
1092 | for (const auto &gattChar : std::as_const(t: dbusServiceData.characteristics)) { |
1093 | if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID())) |
1094 | continue; |
1095 | |
1096 | for (const auto &gattDesc : std::as_const(t: gattChar.descriptors)) { |
1097 | if (descUuid != QBluetoothUuid(gattDesc->uUID())) |
1098 | continue; |
1099 | |
1100 | QDBusPendingReply<QByteArray> reply = gattDesc->ReadValue(options: QVariantMap()); |
1101 | QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); |
1102 | connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, |
1103 | context: this, slot: &QLowEnergyControllerPrivateBluezDBus::onDescReadFinished); |
1104 | foundDesc = true; |
1105 | break; |
1106 | } |
1107 | |
1108 | if (foundDesc) |
1109 | break; |
1110 | } |
1111 | |
1112 | if (!foundDesc) { |
1113 | qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor for reading. Skipping." ; |
1114 | prepareNextJob(); |
1115 | return; |
1116 | } |
1117 | } else if (nextJob.flags.testFlag(flag: GattJob::DescWrite)) { |
1118 | // descriptor writing *************************************** |
1119 | const QLowEnergyCharacteristic ch = characteristicForHandle(handle: nextJob.handle); |
1120 | if (!ch.isValid()) { |
1121 | qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleWriteDesc 1). Skipping." ; |
1122 | prepareNextJob(); |
1123 | return; |
1124 | } |
1125 | |
1126 | const QLowEnergyServicePrivate::CharData &charData = |
1127 | service->characteristicList.value(key: ch.attributeHandle()); |
1128 | if (!charData.descriptorList.contains(key: nextJob.handle)) { |
1129 | qCWarning(QT_BT_BLUEZ) << "Invalid GATT job (scheduleWriteDesc 2). Skipping." ; |
1130 | prepareNextJob(); |
1131 | return; |
1132 | } |
1133 | |
1134 | const QBluetoothUuid descUuid = charData.descriptorList[nextJob.handle].uuid; |
1135 | bool foundDesc = false; |
1136 | for (const auto &gattChar : std::as_const(t: dbusServiceData.characteristics)) { |
1137 | if (charData.uuid != QBluetoothUuid(gattChar.characteristic->uUID())) |
1138 | continue; |
1139 | |
1140 | for (const auto &gattDesc : std::as_const(t: gattChar.descriptors)) { |
1141 | if (descUuid != QBluetoothUuid(gattDesc->uUID())) |
1142 | continue; |
1143 | |
1144 | //notifications enabled via characteristics Start/StopNotify() functions |
1145 | //otherwise regular WriteValue() calls on descriptor interface |
1146 | if (descUuid == QBluetoothUuid(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration)) { |
1147 | const QByteArray value = nextJob.value; |
1148 | |
1149 | QDBusPendingReply<> reply; |
1150 | qCDebug(QT_BT_BLUEZ) << "Init CCC change to" << value.toHex() |
1151 | << charData.uuid << service->uuid; |
1152 | if (value == QByteArray::fromHex(hexEncoded: "0100" ) || value == QByteArray::fromHex(hexEncoded: "0200" )) |
1153 | reply = gattChar.characteristic->StartNotify(); |
1154 | else |
1155 | reply = gattChar.characteristic->StopNotify(); |
1156 | QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); |
1157 | connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, |
1158 | context: this, slot: &QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished); |
1159 | } else { |
1160 | QDBusPendingReply<> reply = gattDesc->WriteValue(value: nextJob.value, options: QVariantMap()); |
1161 | QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); |
1162 | connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, |
1163 | context: this, slot: &QLowEnergyControllerPrivateBluezDBus::onDescWriteFinished); |
1164 | |
1165 | } |
1166 | |
1167 | foundDesc = true; |
1168 | break; |
1169 | } |
1170 | |
1171 | if (foundDesc) |
1172 | break; |
1173 | } |
1174 | |
1175 | if (!foundDesc) { |
1176 | qCWarning(QT_BT_BLUEZ) << "Cannot find descriptor for writing. Skipping." ; |
1177 | prepareNextJob(); |
1178 | return; |
1179 | } |
1180 | } else { |
1181 | qCWarning(QT_BT_BLUEZ) << "Unknown gatt job type. Skipping." ; |
1182 | prepareNextJob(); |
1183 | } |
1184 | } |
1185 | |
1186 | void QLowEnergyControllerPrivateBluezDBus::readCharacteristic( |
1187 | const QSharedPointer<QLowEnergyServicePrivate> service, |
1188 | const QLowEnergyHandle charHandle) |
1189 | { |
1190 | Q_ASSERT(!service.isNull()); |
1191 | if (!service->characteristicList.contains(key: charHandle)) { |
1192 | qCWarning(QT_BT_BLUEZ) << "Read characteristic does not belong to service" |
1193 | << service->uuid; |
1194 | return; |
1195 | } |
1196 | |
1197 | const QLowEnergyServicePrivate::CharData &charDetails |
1198 | = service->characteristicList[charHandle]; |
1199 | if (!(charDetails.properties & QLowEnergyCharacteristic::Read)) { |
1200 | // if this succeeds the device has a bug, char is advertised as |
1201 | // non-readable. We try to be permissive and let the remote |
1202 | // device answer to the read attempt |
1203 | qCWarning(QT_BT_BLUEZ) << "Reading non-readable char" << charHandle; |
1204 | } |
1205 | |
1206 | const GattService &gattService = dbusServices[service->uuid]; |
1207 | if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) { |
1208 | // Reread from dbus interface and write to local cache |
1209 | const QByteArray newValue(1, char(gattService.batteryInterface->percentage())); |
1210 | quint16 result = updateValueOfCharacteristic(charHandle, value: newValue, appendValue: false); |
1211 | if (result > 0) { |
1212 | QLowEnergyCharacteristic ch(service, charHandle); |
1213 | emit service->characteristicRead(info: ch, value: newValue); |
1214 | } else { |
1215 | service->setError(QLowEnergyService::CharacteristicReadError); |
1216 | } |
1217 | return; |
1218 | } |
1219 | |
1220 | GattJob job; |
1221 | job.flags = GattJob::JobFlags({GattJob::CharRead}); |
1222 | job.service = service; |
1223 | job.handle = charHandle; |
1224 | jobs.append(t: job); |
1225 | |
1226 | scheduleNextJob(); |
1227 | } |
1228 | |
1229 | void QLowEnergyControllerPrivateBluezDBus::readDescriptor( |
1230 | const QSharedPointer<QLowEnergyServicePrivate> service, |
1231 | const QLowEnergyHandle charHandle, |
1232 | const QLowEnergyHandle descriptorHandle) |
1233 | { |
1234 | Q_ASSERT(!service.isNull()); |
1235 | if (!service->characteristicList.contains(key: charHandle)) |
1236 | return; |
1237 | |
1238 | const QLowEnergyServicePrivate::CharData &charDetails |
1239 | = service->characteristicList[charHandle]; |
1240 | if (!charDetails.descriptorList.contains(key: descriptorHandle)) |
1241 | return; |
1242 | |
1243 | const GattService &gattService = dbusServices[service->uuid]; |
1244 | if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) { |
1245 | auto descriptor = descriptorForHandle(handle: descriptorHandle); |
1246 | if (descriptor.isValid()) |
1247 | emit service->descriptorRead(info: descriptor, value: descriptor.value()); |
1248 | else |
1249 | service->setError(QLowEnergyService::DescriptorReadError); |
1250 | |
1251 | return; |
1252 | } |
1253 | |
1254 | GattJob job; |
1255 | job.flags = GattJob::JobFlags({GattJob::DescRead}); |
1256 | job.service = service; |
1257 | job.handle = descriptorHandle; |
1258 | jobs.append(t: job); |
1259 | |
1260 | scheduleNextJob(); |
1261 | } |
1262 | |
1263 | void QLowEnergyControllerPrivateBluezDBus::writeCharacteristic( |
1264 | const QSharedPointer<QLowEnergyServicePrivate> service, |
1265 | const QLowEnergyHandle charHandle, |
1266 | const QByteArray &newValue, |
1267 | QLowEnergyService::WriteMode writeMode) |
1268 | { |
1269 | Q_ASSERT(!service.isNull()); |
1270 | if (!service->characteristicList.contains(key: charHandle)) { |
1271 | qCWarning(QT_BT_BLUEZ) << "Write characteristic does not belong to service" |
1272 | << service->uuid; |
1273 | return; |
1274 | } |
1275 | |
1276 | if (role == QLowEnergyController::CentralRole) { |
1277 | const GattService &gattService = dbusServices[service->uuid]; |
1278 | if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) { |
1279 | //Battery1 interface is readonly |
1280 | service->setError(QLowEnergyService::CharacteristicWriteError); |
1281 | return; |
1282 | } |
1283 | |
1284 | |
1285 | GattJob job; |
1286 | job.flags = GattJob::JobFlags({GattJob::CharWrite}); |
1287 | job.service = service; |
1288 | job.handle = charHandle; |
1289 | job.value = newValue; |
1290 | job.writeMode = writeMode; |
1291 | jobs.append(t: job); |
1292 | |
1293 | scheduleNextJob(); |
1294 | } else { |
1295 | // Peripheral role |
1296 | Q_ASSERT(peripheralApplication); |
1297 | if (!peripheralApplication->localCharacteristicWrite(handle: charHandle, value: newValue)) { |
1298 | qCWarning(QT_BT_BLUEZ) << "Characteristic write failed" |
1299 | << characteristicForHandle(handle: charHandle).uuid(); |
1300 | service->setError(QLowEnergyService::CharacteristicWriteError); |
1301 | return; |
1302 | } |
1303 | QLowEnergyServicePrivate::CharData &charData = service->characteristicList[charHandle]; |
1304 | charData.value = newValue; |
1305 | } |
1306 | } |
1307 | |
1308 | void QLowEnergyControllerPrivateBluezDBus::writeDescriptor( |
1309 | const QSharedPointer<QLowEnergyServicePrivate> service, |
1310 | const QLowEnergyHandle charHandle, |
1311 | const QLowEnergyHandle descriptorHandle, |
1312 | const QByteArray &newValue) |
1313 | { |
1314 | Q_ASSERT(!service.isNull()); |
1315 | if (!service->characteristicList.contains(key: charHandle)) |
1316 | return; |
1317 | |
1318 | if (role == QLowEnergyController::CentralRole) { |
1319 | const GattService &gattService = dbusServices[service->uuid]; |
1320 | if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) { |
1321 | auto descriptor = descriptorForHandle(handle: descriptorHandle); |
1322 | if (!descriptor.isValid()) |
1323 | return; |
1324 | |
1325 | if (descriptor.uuid() == QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration) { |
1326 | if (newValue == QByteArray::fromHex(hexEncoded: "0000" ) |
1327 | || newValue == QByteArray::fromHex(hexEncoded: "0100" ) |
1328 | || newValue == QByteArray::fromHex(hexEncoded: "0200" )) { |
1329 | quint16 result = updateValueOfDescriptor(charHandle, descriptorHandle, value: newValue, appendValue: false); |
1330 | if (result > 0) |
1331 | emit service->descriptorWritten(descriptor, newValue); |
1332 | else |
1333 | emit service->setError(QLowEnergyService::DescriptorWriteError); |
1334 | |
1335 | } |
1336 | } else { |
1337 | service->setError(QLowEnergyService::DescriptorWriteError); |
1338 | } |
1339 | |
1340 | return; |
1341 | } |
1342 | |
1343 | GattJob job; |
1344 | job.flags = GattJob::JobFlags({GattJob::DescWrite}); |
1345 | job.service = service; |
1346 | job.handle = descriptorHandle; |
1347 | job.value = newValue; |
1348 | jobs.append(t: job); |
1349 | |
1350 | scheduleNextJob(); |
1351 | } else { |
1352 | // Peripheral role |
1353 | Q_ASSERT(peripheralApplication); |
1354 | |
1355 | auto desc = descriptorForHandle(handle: descriptorHandle); |
1356 | if (desc.uuid() == QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration) { |
1357 | qCWarning(QT_BT_BLUEZ) << "CCCD write not supported in peripheral role" ; |
1358 | service->setError(QLowEnergyService::DescriptorWriteError); |
1359 | return; |
1360 | } else if (!peripheralApplication->localDescriptorWrite(handle: descriptorHandle, value: newValue)) { |
1361 | qCWarning(QT_BT_BLUEZ) << "Descriptor write failed" << desc.uuid(); |
1362 | service->setError(QLowEnergyService::DescriptorWriteError); |
1363 | return; |
1364 | } |
1365 | service->characteristicList[charHandle].descriptorList[descriptorHandle].value = newValue; |
1366 | } |
1367 | } |
1368 | |
1369 | void QLowEnergyControllerPrivateBluezDBus::startAdvertising( |
1370 | const QLowEnergyAdvertisingParameters ¶ms, |
1371 | const QLowEnergyAdvertisingData &advertisingData, |
1372 | const QLowEnergyAdvertisingData &scanResponseData) |
1373 | { |
1374 | error = QLowEnergyController::NoError; |
1375 | errorString.clear(); |
1376 | |
1377 | Q_ASSERT(peripheralApplication); |
1378 | Q_ASSERT(!adapterPathWithPeripheralSupport.isEmpty()); |
1379 | |
1380 | if (advertiser) { |
1381 | // Clear any previous advertiser in case advertising data has changed. |
1382 | // For clarity: this function is called only in 'Unconnected' state |
1383 | delete advertiser; |
1384 | advertiser = nullptr; |
1385 | } |
1386 | advertiser = new QLeDBusAdvertiser(params, advertisingData, scanResponseData, |
1387 | adapterPathWithPeripheralSupport, this); |
1388 | connect(sender: advertiser, signal: &QLeDBusAdvertiser::errorOccurred, |
1389 | context: this, slot: &QLowEnergyControllerPrivateBluezDBus::handleAdvertisingError); |
1390 | |
1391 | setState(QLowEnergyController::AdvertisingState); |
1392 | |
1393 | // First register the application to bluez if needed, and then start the advertisement. |
1394 | // The application registration may fail and is asynchronous => serialize the steps. |
1395 | // For clarity: advertisements can be used without any services, but registering such |
1396 | // application to Bluez would fail |
1397 | if (peripheralApplication->registrationNeeded()) |
1398 | peripheralApplication->registerApplication(); |
1399 | else |
1400 | advertiser->startAdvertising(); |
1401 | } |
1402 | |
1403 | void QLowEnergyControllerPrivateBluezDBus::stopAdvertising() |
1404 | { |
1405 | // This function is called only in Advertising state |
1406 | setState(QLowEnergyController::UnconnectedState); |
1407 | if (advertiser) { |
1408 | advertiser->stopAdvertising(); |
1409 | delete advertiser; |
1410 | advertiser = nullptr; |
1411 | } |
1412 | } |
1413 | |
1414 | void QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationRegistered() |
1415 | { |
1416 | // Start the actual advertising now that the application is registered. |
1417 | // Check the state first in case user has called stopAdvertising() during |
1418 | // application registration |
1419 | if (advertiser && state == QLowEnergyController::AdvertisingState) |
1420 | advertiser->startAdvertising(); |
1421 | else |
1422 | peripheralApplication->unregisterApplication(); |
1423 | } |
1424 | |
1425 | void QLowEnergyControllerPrivateBluezDBus::handlePeripheralCharacteristicValueUpdate( |
1426 | QLowEnergyHandle handle, const QByteArray& value) |
1427 | { |
1428 | const auto characteristic = characteristicForHandle(handle); |
1429 | if (characteristic.d_ptr |
1430 | && updateValueOfCharacteristic(charHandle: handle, value, appendValue: false) == value.size()) { |
1431 | emit characteristic.d_ptr->characteristicChanged(characteristic, newValue: value); |
1432 | } else { |
1433 | qCWarning(QT_BT_BLUEZ) << "Remote characteristic write failed" ; |
1434 | } |
1435 | } |
1436 | |
1437 | void QLowEnergyControllerPrivateBluezDBus::handlePeripheralDescriptorValueUpdate( |
1438 | QLowEnergyHandle characteristicHandle, |
1439 | QLowEnergyHandle descriptorHandle, |
1440 | const QByteArray& value) |
1441 | { |
1442 | const auto descriptor = descriptorForHandle(handle: descriptorHandle); |
1443 | if (descriptor.d_ptr && updateValueOfDescriptor( |
1444 | charHandle: characteristicHandle, descriptorHandle, value, appendValue: false) == value.size()) { |
1445 | emit descriptor.d_ptr->descriptorWritten(descriptor, newValue: value); |
1446 | } else { |
1447 | qCWarning(QT_BT_BLUEZ) << "Remote descriptor write failed" ; |
1448 | } |
1449 | } |
1450 | |
1451 | void QLowEnergyControllerPrivateBluezDBus::handlePeripheralRemoteDeviceChanged( |
1452 | const QBluetoothAddress& address, |
1453 | const QString& name, |
1454 | quint16 mtu) |
1455 | { |
1456 | remoteDevice = address; |
1457 | remoteName = name; |
1458 | remoteMtu = mtu; |
1459 | } |
1460 | |
1461 | void QLowEnergyControllerPrivateBluezDBus::handleAdvertisingError() |
1462 | { |
1463 | Q_ASSERT(peripheralApplication); |
1464 | qCWarning(QT_BT_BLUEZ) << "An advertising error occurred" ; |
1465 | setError(QLowEnergyController::AdvertisingError); |
1466 | setState(QLowEnergyController::UnconnectedState); |
1467 | peripheralApplication->unregisterApplication(); |
1468 | } |
1469 | |
1470 | void QLowEnergyControllerPrivateBluezDBus::handlePeripheralApplicationError() |
1471 | { |
1472 | qCWarning(QT_BT_BLUEZ) << "A Bluez peripheral application error occurred" ; |
1473 | setError(QLowEnergyController::UnknownError); |
1474 | setState(QLowEnergyController::UnconnectedState); |
1475 | } |
1476 | |
1477 | void QLowEnergyControllerPrivateBluezDBus::handlePeripheralConnectivityChanged(bool connected) |
1478 | { |
1479 | Q_Q(QLowEnergyController); |
1480 | qCDebug(QT_BT_BLUEZ) << "Peripheral application connected change to:" << connected; |
1481 | if (connected) { |
1482 | setState(QLowEnergyController::ConnectedState); |
1483 | } else { |
1484 | resetController(); |
1485 | remoteDevice.clear(); |
1486 | setState(QLowEnergyController::UnconnectedState); |
1487 | emit q->disconnected(); |
1488 | } |
1489 | } |
1490 | |
1491 | void QLowEnergyControllerPrivateBluezDBus::requestConnectionUpdate( |
1492 | const QLowEnergyConnectionParameters & /* params */) |
1493 | { |
1494 | qCWarning(QT_BT_BLUEZ) << "Connection udpate requests not supported on Bluez DBus" ; |
1495 | } |
1496 | |
1497 | void QLowEnergyControllerPrivateBluezDBus::addToGenericAttributeList( |
1498 | const QLowEnergyServiceData &serviceData, |
1499 | QLowEnergyHandle startHandle) |
1500 | { |
1501 | Q_ASSERT(peripheralApplication); |
1502 | QSharedPointer<QLowEnergyServicePrivate> servicePrivate = serviceForHandle(handle: startHandle); |
1503 | if (servicePrivate.isNull()) |
1504 | return; |
1505 | peripheralApplication->addService(serviceData, servicePrivate, serviceHandle: startHandle); |
1506 | } |
1507 | |
1508 | int QLowEnergyControllerPrivateBluezDBus::mtu() const |
1509 | { |
1510 | // currently only supported on peripheral role |
1511 | return remoteMtu; |
1512 | } |
1513 | |
1514 | QT_END_NAMESPACE |
1515 | |
1516 | #include "moc_qlowenergycontroller_bluezdbus_p.cpp" |
1517 | |