Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qlowenergycontroller_darwin.mm
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// Copyright (C) 2016 Javier S. Pedro <maemo@javispedro.com>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
6#include "darwin/btperipheralmanager_p.h"
8#include "darwin/btcentralmanager_p.h"
9#include "darwin/btutility_p.h"
10#include "darwin/uistrings_p.h"
11
16#include "qbluetoothuuid.h"
17
18#include <QtCore/qloggingcategory.h>
19#include <QtCore/qsharedpointer.h>
20#include <QtCore/qbytearray.h>
21#include <QtCore/qglobal.h>
22#include <QtCore/qstring.h>
23#include <QtCore/qlist.h>
24#include <QtCore/qcoreapplication.h>
25#include <QtCore/qpermissions.h>
26
27QT_BEGIN_NAMESPACE
28
29namespace {
30
31typedef QSharedPointer<QLowEnergyServicePrivate> ServicePrivate;
32
33// Convenience function, can return a smart pointer that 'isNull'.
34ServicePrivate qt_createLEService(QLowEnergyControllerPrivateDarwin *controller, CBService *cbService, bool included)
35{
36 Q_ASSERT_X(controller, Q_FUNC_INFO, "invalid controller (null)");
37 Q_ASSERT_X(cbService, Q_FUNC_INFO, "invalid service (nil)");
38
39 CBUUID *const cbUuid = cbService.UUID;
40 if (!cbUuid) {
41 qCDebug(QT_BT_DARWIN) << "invalid service, UUID is nil";
42 return ServicePrivate();
43 }
44
45 const QBluetoothUuid qtUuid(DarwinBluetooth::qt_uuid(cbUuid));
46 if (qtUuid.isNull()) // Conversion error is reported by qt_uuid.
47 return ServicePrivate();
48
49 ServicePrivate newService(new QLowEnergyServicePrivate);
50 newService->uuid = qtUuid;
51 newService->setController(controller);
52
53 if (included)
54 newService->type |= QLowEnergyService::IncludedService;
55
56 // TODO: isPrimary is ... always 'NO' - to be investigated.
57 /*
58 if (!cbService.isPrimary) {
59 // Our guess included/not was probably wrong.
60 newService->type &= ~QLowEnergyService::PrimaryService;
61 newService->type |= QLowEnergyService::IncludedService;
62 }
63 */
64 return newService;
65}
66
67typedef QList<QBluetoothUuid> UUIDList;
68
69UUIDList qt_servicesUuids(NSArray *services)
70{
72
73 if (!services || !services.count)
74 return UUIDList();
75
76 UUIDList uuids;
77
78 for (CBService *s in services)
79 uuids.append(DarwinBluetooth::qt_uuid(s.UUID));
80
81 return uuids;
82}
83
84} // unnamed namespace
85
86QLowEnergyControllerPrivateDarwin::QLowEnergyControllerPrivateDarwin()
87{
88 void registerQLowEnergyControllerMetaType();
89 registerQLowEnergyControllerMetaType();
90 qRegisterMetaType<QLowEnergyHandle>("QLowEnergyHandle");
91 qRegisterMetaType<QSharedPointer<QLowEnergyServicePrivate>>();
92}
93
94QLowEnergyControllerPrivateDarwin::~QLowEnergyControllerPrivateDarwin()
95{
96 if (const auto leQueue = DarwinBluetooth::qt_LE_queue()) {
97 if (role == QLowEnergyController::CentralRole) {
98 const auto manager = centralManager.getAs<DarwinBTCentralManager>();
99 dispatch_sync(leQueue, ^{
100 [manager detach];
101 });
102 } else {
103 const auto manager = peripheralManager.getAs<DarwinBTPeripheralManager>();
104 dispatch_sync(leQueue, ^{
105 [manager detach];
106 });
107 }
108 }
109}
110
111bool QLowEnergyControllerPrivateDarwin::isValid() const
112{
113 return centralManager || peripheralManager;
114}
115
116void QLowEnergyControllerPrivateDarwin::init()
117{
118 // We have to override the 'init', it's pure virtual in the base.
119 // Just creating a central or peripheral should not trigger any
120 // error yet.
121}
122
123bool QLowEnergyControllerPrivateDarwin::lazyInit()
124{
125 using namespace DarwinBluetooth;
126
127 if (peripheralManager || centralManager)
128 return true;
129
130 if (qApp->checkPermission(QBluetoothPermission{}) != Qt::PermissionStatus::Granted) {
131 qCWarning(QT_BT_DARWIN,
132 "Use of Bluetooth LE must be explicitly requested by the application.");
133 setError(QLowEnergyController::MissingPermissionsError);
134 return false;
135 }
136
137 std::unique_ptr<LECBManagerNotifier> notifier = std::make_unique<LECBManagerNotifier>();
138 if (role == QLowEnergyController::PeripheralRole) {
139 peripheralManager.reset([[DarwinBTPeripheralManager alloc] initWith:notifier.get()],
140 DarwinBluetooth::RetainPolicy::noInitialRetain);
141 Q_ASSERT(peripheralManager);
142 } else {
143 centralManager.reset([[DarwinBTCentralManager alloc] initWith:notifier.get()],
144 DarwinBluetooth::RetainPolicy::noInitialRetain);
145 Q_ASSERT(centralManager);
146 }
147
148 // FIXME: Q_UNLIKELY
149 if (!connectSlots(notifier.get()))
150 qCWarning(QT_BT_DARWIN) << "failed to connect to notifier's signal(s)";
151
152 // Ownership was taken by central manager.
153 notifier.release();
154
155 return true;
156}
157
158void QLowEnergyControllerPrivateDarwin::connectToDevice()
159{
160 Q_ASSERT_X(state == QLowEnergyController::UnconnectedState,
161 Q_FUNC_INFO, "invalid state");
162
163 if (deviceUuid.isNull()) {
164 // Wrong constructor was used or invalid UUID was provided.
165 return _q_CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
166 }
167
168 if (!lazyInit()) // MissingPermissionsError was emit.
169 return;
170
171 // The logic enforcing the role is in the public class.
172 Q_ASSERT_X(role != QLowEnergyController::PeripheralRole,
173 Q_FUNC_INFO, "invalid role (peripheral)");
174
175 dispatch_queue_t leQueue(DarwinBluetooth::qt_LE_queue());
176 Q_ASSERT_X(leQueue, Q_FUNC_INFO, "invalid LE queue (nullptr)");
177
178 setError(QLowEnergyController::NoError);
179 setState(QLowEnergyController::ConnectingState);
180
181 const QBluetoothUuid deviceUuidCopy(deviceUuid);
182 DarwinBTCentralManager *manager = centralManager.getAs<DarwinBTCentralManager>();
183 dispatch_async(leQueue, ^{
184 [manager connectToDevice:deviceUuidCopy];
185 });
186}
187
188void QLowEnergyControllerPrivateDarwin::disconnectFromDevice()
189{
190 Q_ASSERT(isValid()); // Check for proper state is in Qt's code.
191
192 if (role == QLowEnergyController::PeripheralRole) {
193 // CoreBluetooth API intentionally does not provide any way of closing
194 // a connection. All we can do here is to stop the advertisement.
195 return stopAdvertising();
196 }
197
198 const auto oldState = state;
199
200 if (dispatch_queue_t leQueue = DarwinBluetooth::qt_LE_queue()) {
201 setState(QLowEnergyController::ClosingState);
202 invalidateServices();
203
204 auto manager = centralManager.getAs<DarwinBTCentralManager>();
205 dispatch_async(leQueue, ^{
206 [manager disconnectFromDevice];
207 });
208
209 if (oldState == QLowEnergyController::ConnectingState) {
210 // With a pending connect attempt there is no
211 // guarantee we'll ever have didDisconnect callback,
212 // set the state here and now to make sure we still
213 // can connect.
214 setState(QLowEnergyController::UnconnectedState);
215 }
216 } else {
217 qCCritical(QT_BT_DARWIN) << "qt LE queue is nil, "
218 "can not dispatch 'disconnect'";
219 }
220}
221
222void QLowEnergyControllerPrivateDarwin::discoverServices()
223{
224 Q_ASSERT_X(state != QLowEnergyController::UnconnectedState,
225 Q_FUNC_INFO, "not connected to peripheral");
226 Q_ASSERT_X(role != QLowEnergyController::PeripheralRole,
227 Q_FUNC_INFO, "invalid role (peripheral)");
228
229 Q_ASSERT(isValid()); // Check we're in a proper state is in q's code.
230
231 dispatch_queue_t leQueue(DarwinBluetooth::qt_LE_queue());
232 Q_ASSERT_X(leQueue, Q_FUNC_INFO, "LE queue not found");
233
234 setState(QLowEnergyController::DiscoveringState);
235
236 DarwinBTCentralManager *manager = centralManager.getAs<DarwinBTCentralManager>();
237 dispatch_async(leQueue, ^{
238 [manager discoverServices];
239 });
240}
241
242void QLowEnergyControllerPrivateDarwin::discoverServiceDetails(
243 const QBluetoothUuid &serviceUuid, QLowEnergyService::DiscoveryMode mode)
244{
245 Q_UNUSED(mode);
246 if (state != QLowEnergyController::DiscoveredState) {
247 qCWarning(QT_BT_DARWIN) << "can not discover service details in the current state, "
248 "QLowEnergyController::DiscoveredState is expected";
249 return;
250 }
251
252 if (!serviceList.contains(serviceUuid)) {
253 qCWarning(QT_BT_DARWIN) << "unknown service: " << serviceUuid;
254 return;
255 }
256
257 dispatch_queue_t leQueue(DarwinBluetooth::qt_LE_queue());
258 Q_ASSERT(leQueue);
259
260 ServicePrivate qtService(serviceList.value(serviceUuid));
261 qtService->setState(QLowEnergyService::RemoteServiceDiscovering);
262 // Copy objects ...
263 DarwinBTCentralManager *manager = centralManager.getAs<DarwinBTCentralManager>();
264 const QBluetoothUuid serviceUuidCopy(serviceUuid);
265 dispatch_async(leQueue, ^{
266 [manager discoverServiceDetails:serviceUuidCopy readValues:mode == QLowEnergyService::FullDiscovery];
267 });
268}
269
270void QLowEnergyControllerPrivateDarwin::requestConnectionUpdate(const QLowEnergyConnectionParameters &params)
271{
272 Q_UNUSED(params);
273 // TODO: implement this, if possible.
274 qCWarning(QT_BT_DARWIN) << "Connection update not implemented on your platform";
275}
276
277void QLowEnergyControllerPrivateDarwin::addToGenericAttributeList(const QLowEnergyServiceData &service,
278 QLowEnergyHandle startHandle)
279{
280 // Darwin LE controller implements the addServiceHelper() for adding services, and thus
281 // the base class' addServiceHelper(), which calls this function, is not used
282 Q_UNUSED(service);
283 Q_UNUSED(startHandle);
284}
285
286int QLowEnergyControllerPrivateDarwin::mtu() const
287{
288 // FIXME: check the state - neither public class does,
289 // nor us - not fun! E.g. readRssi correctly checked/asserted.
290
291 __block int mtu = DarwinBluetooth::defaultMtu;
292 if (!isValid()) // A minimal check.
293 return defaultMtu;
294
295 if (const auto leQueue = DarwinBluetooth::qt_LE_queue()) {
296 const auto *manager = centralManager.getAs<DarwinBTCentralManager>();
297 dispatch_sync(leQueue, ^{
298 mtu = [manager mtu];
299 });
300 }
301
302 return mtu;
303}
304
305void QLowEnergyControllerPrivateDarwin::readRssi()
306{
307 Q_ASSERT(role == QLowEnergyController::CentralRole);
308 Q_ASSERT(state == QLowEnergyController::ConnectedState ||
309 state == QLowEnergyController::DiscoveringState ||
310 state == QLowEnergyController::DiscoveredState);
311
312 if (const auto leQueue = DarwinBluetooth::qt_LE_queue()) {
313 const auto *manager = centralManager.getAs<DarwinBTCentralManager>();
314 dispatch_async(leQueue, ^{
315 [manager readRssi];
316 });
317 }
318}
319
320QLowEnergyService * QLowEnergyControllerPrivateDarwin::addServiceHelper(const QLowEnergyServiceData &service)
321{
322 if (!lazyInit() || !isValid()) {
323 qCWarning(QT_BT_DARWIN) << "invalid peripheral";
324 return nullptr;
325 }
326
327 for (auto includedService : service.includedServices())
328 includedService->d_ptr->type |= QLowEnergyService::IncludedService;
329
330 const auto manager = peripheralManager.getAs<DarwinBTPeripheralManager>();
331 Q_ASSERT(manager);
332 if (const auto servicePrivate = [manager addService:service]) {
333 servicePrivate->setController(this);
334 servicePrivate->state = QLowEnergyService::LocalService;
335 localServices.insert(servicePrivate->uuid, servicePrivate);
336 return new QLowEnergyService(servicePrivate);
337 }
338
339 return nullptr;
340}
341
342void QLowEnergyControllerPrivateDarwin::_q_connected()
343{
344 setState(QLowEnergyController::ConnectedState);
345 emit q_ptr->connected();
346}
347
348void QLowEnergyControllerPrivateDarwin::_q_disconnected()
349{
350 if (role == QLowEnergyController::CentralRole)
351 invalidateServices();
352
353 setState(QLowEnergyController::UnconnectedState);
354 emit q_ptr->disconnected();
355}
356
357void QLowEnergyControllerPrivateDarwin::_q_mtuChanged(int newValue)
358{
359 emit q_ptr->mtuChanged(newValue);
360}
361
362void QLowEnergyControllerPrivateDarwin::_q_serviceDiscoveryFinished()
363{
364 Q_ASSERT_X(state == QLowEnergyController::DiscoveringState,
365 Q_FUNC_INFO, "invalid state");
366
367 using namespace DarwinBluetooth;
368
370
371 NSArray *const services = [centralManager.getAs<DarwinBTCentralManager>() peripheral].services;
372 // Now we have to traverse the discovered services tree.
373 // Essentially it's an iterative version of more complicated code from the
374 // DarwinBTCentralManager's code.
375 // All Obj-C entities either auto-release, or guarded by ObjCScopedReferences.
376 if (services && [services count]) {
377 QMap<QBluetoothUuid, CBService *> discoveredCBServices;
378 //1. The first pass - none of this services is 'included' yet (we'll discover 'included'
379 // during the pass 2); we also ignore duplicates (== services with the same UUID)
380 // - since we do not have a way to distinguish them later
381 // (our API is using uuids when creating QLowEnergyServices).
382 for (CBService *cbService in services) {
383 const ServicePrivate newService(qt_createLEService(this, cbService, false));
384 if (!newService.data())
385 continue;
386 if (serviceList.contains(newService->uuid)) {
387 // It's a bit stupid we first created it ...
388 qCDebug(QT_BT_DARWIN) << "discovered service with a duplicated UUID"
389 << newService->uuid;
390 continue;
391 }
392 serviceList.insert(newService->uuid, newService);
393 discoveredCBServices.insert(newService->uuid, cbService);
394 }
395
396 ObjCStrongReference<NSMutableArray> toVisit([[NSMutableArray alloc] initWithArray:services], RetainPolicy::noInitialRetain);
397 ObjCStrongReference<NSMutableArray> toVisitNext([[NSMutableArray alloc] init], RetainPolicy::noInitialRetain);
398 ObjCStrongReference<NSMutableSet> visited([[NSMutableSet alloc] init], RetainPolicy::noInitialRetain);
399
400 while (true) {
401 for (NSUInteger i = 0, e = [toVisit count]; i < e; ++i) {
402 CBService *const s = [toVisit objectAtIndex:i];
403 if (![visited containsObject:s]) {
404 [visited addObject:s];
405 if (s.includedServices && s.includedServices.count)
406 [toVisitNext addObjectsFromArray:s.includedServices];
407 }
408
409 const QBluetoothUuid uuid(qt_uuid(s.UUID));
410 if (serviceList.contains(uuid) && discoveredCBServices.value(uuid) == s) {
411 ServicePrivate qtService(serviceList.value(uuid));
412 // Add included UUIDs:
413 qtService->includedServices.append(qt_servicesUuids(s.includedServices));
414 }// Else - we ignored this CBService object.
415 }
416
417 if (![toVisitNext count])
418 break;
419
420 for (NSUInteger i = 0, e = [toVisitNext count]; i < e; ++i) {
421 CBService *const s = [toVisitNext objectAtIndex:i];
422 const QBluetoothUuid uuid(qt_uuid(s.UUID));
423 if (serviceList.contains(uuid)) {
424 if (discoveredCBServices.value(uuid) == s) {
425 ServicePrivate qtService(serviceList.value(uuid));
426 qtService->type |= QLowEnergyService::IncludedService;
427 } // Else this is the duplicate we ignored already.
428 } else {
429 // Oh, we do not even have it yet???
430 ServicePrivate newService(qt_createLEService(this, s, true));
431 serviceList.insert(newService->uuid, newService);
432 discoveredCBServices.insert(newService->uuid, s);
433 }
434 }
435
436 toVisit.swap(toVisitNext);
437 toVisitNext.reset([[NSMutableArray alloc] init], RetainPolicy::noInitialRetain);
438 }
439 } else {
440 qCDebug(QT_BT_DARWIN) << "no services found";
441 }
442
443
444 state = QLowEnergyController::DiscoveredState;
445
446 for (auto it = serviceList.constBegin(); it != serviceList.constEnd(); ++it) {
447 QMetaObject::invokeMethod(q_ptr, "serviceDiscovered", Qt::QueuedConnection,
448 Q_ARG(QBluetoothUuid, it.key()));
449 }
450
451 QMetaObject::invokeMethod(q_ptr, "stateChanged", Qt::QueuedConnection, Q_ARG(QLowEnergyController::ControllerState, state));
452 QMetaObject::invokeMethod(q_ptr, "discoveryFinished", Qt::QueuedConnection);
453}
454
455void QLowEnergyControllerPrivateDarwin::_q_serviceDetailsDiscoveryFinished(QSharedPointer<QLowEnergyServicePrivate> service)
456{
458
459 Q_ASSERT(service);
460
461 if (!serviceList.contains(service->uuid)) {
462 qCDebug(QT_BT_DARWIN) << "unknown service uuid:"
463 << service->uuid;
464 return;
465 }
466
467 ServicePrivate qtService(serviceList.value(service->uuid));
468 // Assert on handles?
469 qtService->startHandle = service->startHandle;
470 qtService->endHandle = service->endHandle;
471 qtService->characteristicList = service->characteristicList;
472
473 qtService->setState(QLowEnergyService::RemoteServiceDiscovered);
474}
475
476void QLowEnergyControllerPrivateDarwin::_q_servicesWereModified()
477{
478 if (!(state == QLowEnergyController::DiscoveringState
479 || state == QLowEnergyController::DiscoveredState)) {
480 qCWarning(QT_BT_DARWIN) << "services were modified while controller is not in Discovered/Discovering state";
481 return;
482 }
483
484 if (state == QLowEnergyController::DiscoveredState)
485 invalidateServices();
486
487 setState(QLowEnergyController::ConnectedState);
488 q_ptr->discoverServices();
489}
490
491void QLowEnergyControllerPrivateDarwin::_q_characteristicRead(QLowEnergyHandle charHandle,
492 const QByteArray &value)
493{
494 Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle(0)");
495
496 ServicePrivate service(serviceForHandle(charHandle));
497 if (service.isNull())
498 return;
499
500 QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle));
501 if (!characteristic.isValid()) {
502 qCWarning(QT_BT_DARWIN) << "unknown characteristic";
503 return;
504 }
505
506 if (characteristic.properties() & QLowEnergyCharacteristic::Read)
507 updateValueOfCharacteristic(charHandle, value, false);
508
509 emit service->characteristicRead(characteristic, value);
510}
511
512void QLowEnergyControllerPrivateDarwin::_q_characteristicWritten(QLowEnergyHandle charHandle,
513 const QByteArray &value)
514{
515 Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle(0)");
516
517 ServicePrivate service(serviceForHandle(charHandle));
518 if (service.isNull()) {
519 qCWarning(QT_BT_DARWIN) << "can not find service for characteristic handle"
520 << charHandle;
521 return;
522 }
523
524 QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle));
525 if (!characteristic.isValid()) {
526 qCWarning(QT_BT_DARWIN) << "unknown characteristic";
527 return;
528 }
529
530 if (characteristic.properties() & QLowEnergyCharacteristic::Read)
531 updateValueOfCharacteristic(charHandle, value, false);
532
533 emit service->characteristicWritten(characteristic, value);
534}
535
536void QLowEnergyControllerPrivateDarwin::_q_characteristicUpdated(QLowEnergyHandle charHandle,
537 const QByteArray &value)
538{
539 // TODO: write/update notifications are quite similar (except asserts/warnings messages
540 // and different signals emitted). Merge them into one function?
541 Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle(0)");
542
543 ServicePrivate service(serviceForHandle(charHandle));
544 if (service.isNull()) {
545 // This can be an error (no characteristic found for this handle),
546 // it can also be that we set notify value before the service
547 // was reported (serviceDetailsDiscoveryFinished) - this happens,
548 // if we read a descriptor (characteristic client configuration),
549 // and it's (pre)set.
550 return;
551 }
552
553 QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle));
554 if (!characteristic.isValid()) {
555 qCWarning(QT_BT_DARWIN) << "unknown characteristic";
556 return;
557 }
558
559 if (characteristic.properties() & QLowEnergyCharacteristic::Read)
560 updateValueOfCharacteristic(charHandle, value, false);
561
562 emit service->characteristicChanged(characteristic, value);
563}
564
565void QLowEnergyControllerPrivateDarwin::_q_descriptorRead(QLowEnergyHandle dHandle,
566 const QByteArray &value)
567{
568 Q_ASSERT_X(dHandle, Q_FUNC_INFO, "invalid descriptor handle (0)");
569
570 const QLowEnergyDescriptor qtDescriptor(descriptorForHandle(dHandle));
571 if (!qtDescriptor.isValid()) {
572 qCWarning(QT_BT_DARWIN) << "unknown descriptor" << dHandle;
573 return;
574 }
575
576 ServicePrivate service(serviceForHandle(qtDescriptor.characteristicHandle()));
577 updateValueOfDescriptor(qtDescriptor.characteristicHandle(), dHandle, value, false);
578 emit service->descriptorRead(qtDescriptor, value);
579}
580
581void QLowEnergyControllerPrivateDarwin::_q_descriptorWritten(QLowEnergyHandle dHandle,
582 const QByteArray &value)
583{
584 Q_ASSERT_X(dHandle, Q_FUNC_INFO, "invalid descriptor handle (0)");
585
586 const QLowEnergyDescriptor qtDescriptor(descriptorForHandle(dHandle));
587 if (!qtDescriptor.isValid()) {
588 qCWarning(QT_BT_DARWIN) << "unknown descriptor" << dHandle;
589 return;
590 }
591
592 ServicePrivate service(serviceForHandle(qtDescriptor.characteristicHandle()));
593 // TODO: test if this data is what we expected.
594 updateValueOfDescriptor(qtDescriptor.characteristicHandle(), dHandle, value, false);
595 emit service->descriptorWritten(qtDescriptor, value);
596}
597
598void QLowEnergyControllerPrivateDarwin::_q_notificationEnabled(QLowEnergyHandle charHandle,
599 bool enabled)
600{
601 // CoreBluetooth in peripheral role does not allow mutable descriptors,
602 // in central we can only call setNotification:enabled/disabled.
603 // But from Qt API's point of view, a central has to write into
604 // client characteristic configuration descriptor. So here we emulate
605 // such a write (we cannot say if it's a notification or indication and
606 // report as both).
607
608 Q_ASSERT_X(role == QLowEnergyController::PeripheralRole, Q_FUNC_INFO,
609 "controller has an invalid role, 'peripheral' expected");
610 Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle (0)");
611
612 const QLowEnergyCharacteristic qtChar(characteristicForHandle(charHandle));
613 if (!qtChar.isValid()) {
614 qCWarning(QT_BT_DARWIN) << "unknown characteristic" << charHandle;
615 return;
616 }
617
618 const QLowEnergyDescriptor qtDescriptor =
619 qtChar.descriptor(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);
620 if (!qtDescriptor.isValid()) {
621 qCWarning(QT_BT_DARWIN) << "characteristic" << charHandle
622 << "does not have a client characteristic "
623 "descriptor";
624 return;
625 }
626
627 ServicePrivate service(serviceForHandle(charHandle));
628 if (service.data()) {
629 // It's a 16-bit value, the least significant bit is for notifications,
630 // the next one - for indications (thus 1 means notifications enabled,
631 // 2 - indications enabled).
632 // 3 is the maximum value and it means both enabled.
633 QByteArray value(2, 0);
634 if (enabled)
635 value[0] = 3;
636 updateValueOfDescriptor(charHandle, qtDescriptor.handle(), value, false);
637 emit service->descriptorWritten(qtDescriptor, value);
638 }
639}
640
641void QLowEnergyControllerPrivateDarwin::_q_LEnotSupported()
642{
643 // Report as an error. But this should not be possible
644 // actually: before connecting to any device, we have
645 // to discover it, if it was discovered ... LE _must_
646 // be supported.
647}
648
649void QLowEnergyControllerPrivateDarwin::_q_CBManagerError(QLowEnergyController::Error errorCode)
650{
651 qCDebug(QT_BT_DARWIN) << "QLowEnergyController error:" << errorCode << "in state:" << state;
652 // This function handles errors reported while connecting to a remote device
653 // and also other errors in general.
654 setError(errorCode);
655
656 if (state == QLowEnergyController::ConnectingState
657 || state == QLowEnergyController::AdvertisingState) {
658 setState(QLowEnergyController::UnconnectedState);
659 } else if (state == QLowEnergyController::DiscoveringState) {
660 // An error occurred during service discovery, finish the discovery.
661 setState(QLowEnergyController::ConnectedState);
662 emit q_ptr->discoveryFinished();
663 }
664
665 // In any other case we stay in Discovered, it's
666 // a service/characteristic - related error.
667}
668
669void QLowEnergyControllerPrivateDarwin::_q_CBManagerError(const QBluetoothUuid &serviceUuid,
670 QLowEnergyController::Error errorCode)
671{
672 // Errors reported while discovering service details etc.
673 Q_UNUSED(errorCode); // TODO: setError?
674
675 // We failed to discover any characteristics/descriptors.
676 if (serviceList.contains(serviceUuid)) {
677 ServicePrivate qtService(serviceList.value(serviceUuid));
678 qtService->setState(QLowEnergyService::InvalidService);
679 } else {
680 qCDebug(QT_BT_DARWIN) << "error reported for unknown service"
681 << serviceUuid;
682 }
683}
684
685void QLowEnergyControllerPrivateDarwin::_q_CBManagerError(const QBluetoothUuid &serviceUuid,
686 QLowEnergyService::ServiceError errorCode)
687{
688 if (!serviceList.contains(serviceUuid)) {
689 qCDebug(QT_BT_DARWIN) << "unknown service uuid:"
690 << serviceUuid;
691 return;
692 }
693
694 ServicePrivate service(serviceList.value(serviceUuid));
695 service->setError(errorCode);
696}
697
698void QLowEnergyControllerPrivateDarwin::setNotifyValue(QSharedPointer<QLowEnergyServicePrivate> service,
699 QLowEnergyHandle charHandle,
700 const QByteArray &newValue)
701{
702 Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
703
704 if (role == QLowEnergyController::PeripheralRole) {
705 qCWarning(QT_BT_DARWIN) << "invalid role (peripheral)";
706 service->setError(QLowEnergyService::DescriptorWriteError);
707 return;
708 }
709
710 if (newValue.size() > 2) {
711 // Qt's API requires an error on such write.
712 // With Core Bluetooth we do not write any descriptor,
713 // but instead call a special method. So it's better to
714 // intercept wrong data size here:
715 qCWarning(QT_BT_DARWIN) << "client characteristic configuration descriptor"
716 "is 2 bytes, but value size is: " << newValue.size();
717 service->setError(QLowEnergyService::DescriptorWriteError);
718 return;
719 }
720
721 if (!serviceList.contains(service->uuid)) {
722 qCWarning(QT_BT_DARWIN) << "no service with uuid:" << service->uuid << "found";
723 return;
724 }
725
726 if (!service->characteristicList.contains(charHandle)) {
727 qCDebug(QT_BT_DARWIN) << "no characteristic with handle:"
728 << charHandle << "found";
729 return;
730 }
731
732 dispatch_queue_t leQueue(DarwinBluetooth::qt_LE_queue());
733 Q_ASSERT_X(leQueue, Q_FUNC_INFO, "no LE queue found");
734
735 DarwinBTCentralManager *manager = centralManager.getAs<DarwinBTCentralManager>();
736 const QBluetoothUuid serviceUuid(service->uuid);
737 const QByteArray newValueCopy(newValue);
738 dispatch_async(leQueue, ^{
739 [manager setNotifyValue:newValueCopy
740 forCharacteristic:charHandle
741 onService:serviceUuid];
742 });
743}
744
745void QLowEnergyControllerPrivateDarwin::readCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service,
746 const QLowEnergyHandle charHandle)
747{
748 Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
749
750 if (role == QLowEnergyController::PeripheralRole) {
751 qCWarning(QT_BT_DARWIN) << "invalid role (peripheral)";
752 return;
753 }
754
755 if (!serviceList.contains(service->uuid)) {
756 qCWarning(QT_BT_DARWIN) << "no service with uuid:"
757 << service->uuid << "found";
758 return;
759 }
760
761 if (!service->characteristicList.contains(charHandle)) {
762 qCDebug(QT_BT_DARWIN) << "no characteristic with handle:"
763 << charHandle << "found";
764 return;
765 }
766
767 dispatch_queue_t leQueue(DarwinBluetooth::qt_LE_queue());
768 Q_ASSERT_X(leQueue, Q_FUNC_INFO, "no LE queue found");
769
770 // Attention! We have to copy UUID.
771 DarwinBTCentralManager *manager = centralManager.getAs<DarwinBTCentralManager>();
772 const QBluetoothUuid serviceUuid(service->uuid);
773 dispatch_async(leQueue, ^{
774 [manager readCharacteristic:charHandle onService:serviceUuid];
775 });
776}
777
778void QLowEnergyControllerPrivateDarwin::writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service,
779 const QLowEnergyHandle charHandle, const QByteArray &newValue,
780 QLowEnergyService::WriteMode mode)
781{
782 Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
783
784 // We can work only with services found on a given peripheral
785 // (== created by the given LE controller).
786
787 if (!serviceList.contains(service->uuid) && !localServices.contains(service->uuid)) {
788 qCWarning(QT_BT_DARWIN) << "no service with uuid:"
789 << service->uuid << " found";
790 return;
791 }
792
793 if (!service->characteristicList.contains(charHandle)) {
794 qCDebug(QT_BT_DARWIN) << "no characteristic with handle:"
795 << charHandle << " found";
796 return;
797 }
798
799 dispatch_queue_t leQueue(DarwinBluetooth::qt_LE_queue());
800 Q_ASSERT_X(leQueue, Q_FUNC_INFO, "no LE queue found");
801 // Attention! We have to copy objects!
802 const QByteArray newValueCopy(newValue);
803 if (role == QLowEnergyController::CentralRole) {
804 const QBluetoothUuid serviceUuid(service->uuid);
805 const auto manager = centralManager.getAs<DarwinBTCentralManager>();
806 dispatch_async(leQueue, ^{
807 [manager write:newValueCopy
808 charHandle:charHandle
809 onService:serviceUuid
810 withResponse:mode == QLowEnergyService::WriteWithResponse];
811 });
812 } else {
813 const auto manager = peripheralManager.getAs<DarwinBTPeripheralManager>();
814 dispatch_async(leQueue, ^{
815 [manager write:newValueCopy charHandle:charHandle];
816 });
817 }
818}
819
820quint16 QLowEnergyControllerPrivateDarwin::updateValueOfCharacteristic(QLowEnergyHandle charHandle,
821 const QByteArray &value,
822 bool appendValue)
823{
824 QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle);
825 if (!service.isNull()) {
826 CharacteristicDataMap::iterator charIt = service->characteristicList.find(charHandle);
827 if (charIt != service->characteristicList.end()) {
828 QLowEnergyServicePrivate::CharData &charData = charIt.value();
829 if (appendValue)
830 charData.value += value;
831 else
832 charData.value = value;
833
834 return charData.value.size();
835 }
836 }
837
838 return 0;
839}
840
841void QLowEnergyControllerPrivateDarwin::readDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service,
842 const QLowEnergyHandle charHandle,
843 const QLowEnergyHandle descriptorHandle)
844{
845 Q_UNUSED(charHandle); // Hehe, yes!
846
847 Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
848
849 if (role == QLowEnergyController::PeripheralRole) {
850 qCWarning(QT_BT_DARWIN) << "invalid role (peripheral)";
851 return;
852 }
853
854 if (!serviceList.contains(service->uuid)) {
855 qCWarning(QT_BT_DARWIN) << "no service with uuid:"
856 << service->uuid << "found";
857 return;
858 }
859
860 dispatch_queue_t leQueue(DarwinBluetooth::qt_LE_queue());
861 if (!leQueue) {
862 qCWarning(QT_BT_DARWIN) << "no LE queue found";
863 return;
864 }
865 // Attention! Copy objects!
866 const QBluetoothUuid serviceUuid(service->uuid);
867 DarwinBTCentralManager * const manager = centralManager.getAs<DarwinBTCentralManager>();
868 dispatch_async(leQueue, ^{
869 [manager readDescriptor:descriptorHandle
870 onService:serviceUuid];
871 });
872}
873
874void QLowEnergyControllerPrivateDarwin::writeDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service,
875 const QLowEnergyHandle charHandle,
876 const QLowEnergyHandle descriptorHandle,
877 const QByteArray &newValue)
878{
879 Q_UNUSED(charHandle);
880
881 Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
882
883 if (role == QLowEnergyController::PeripheralRole) {
884 qCWarning(QT_BT_DARWIN) << "invalid role (peripheral)";
885 return;
886 }
887
888 // We can work only with services found on a given peripheral
889 // (== created by the given LE controller),
890 // otherwise we can not write anything at all.
891 if (!serviceList.contains(service->uuid)) {
892 qCWarning(QT_BT_DARWIN) << "no service with uuid:"
893 << service->uuid << " found";
894 return;
895 }
896
897 dispatch_queue_t leQueue(DarwinBluetooth::qt_LE_queue());
898 Q_ASSERT_X(leQueue, Q_FUNC_INFO, "no LE queue found");
899 // Attention! Copy objects!
900 const QBluetoothUuid serviceUuid(service->uuid);
901 DarwinBTCentralManager * const manager = centralManager.getAs<DarwinBTCentralManager>();
902 const QByteArray newValueCopy(newValue);
903 dispatch_async(leQueue, ^{
904 [manager write:newValueCopy
905 descHandle:descriptorHandle
906 onService:serviceUuid];
907 });
908}
909
910quint16 QLowEnergyControllerPrivateDarwin::updateValueOfDescriptor(QLowEnergyHandle charHandle, QLowEnergyHandle descHandle,
911 const QByteArray &value, bool appendValue)
912{
913 QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle);
914 if (!service.isNull()) {
915 CharacteristicDataMap::iterator charIt = service->characteristicList.find(charHandle);
916 if (charIt != service->characteristicList.end()) {
917 QLowEnergyServicePrivate::CharData &charData = charIt.value();
918
919 DescriptorDataMap::iterator descIt = charData.descriptorList.find(descHandle);
920 if (descIt != charData.descriptorList.end()) {
921 QLowEnergyServicePrivate::DescData &descDetails = descIt.value();
922
923 if (appendValue)
924 descDetails.value += value;
925 else
926 descDetails.value = value;
927
928 return descDetails.value.size();
929 }
930 }
931 }
932
933 return 0;
934}
935
936bool QLowEnergyControllerPrivateDarwin::connectSlots(DarwinBluetooth::LECBManagerNotifier *notifier)
937{
938 using DarwinBluetooth::LECBManagerNotifier;
939
940 Q_ASSERT_X(notifier, Q_FUNC_INFO, "invalid notifier object (null)");
941
942 bool ok = connect(notifier, &LECBManagerNotifier::connected,
943 this, &QLowEnergyControllerPrivateDarwin::_q_connected);
944 ok = ok && connect(notifier, &LECBManagerNotifier::disconnected,
945 this, &QLowEnergyControllerPrivateDarwin::_q_disconnected);
946 ok = ok && connect(notifier, &LECBManagerNotifier::serviceDiscoveryFinished,
947 this, &QLowEnergyControllerPrivateDarwin::_q_serviceDiscoveryFinished);
948 ok = ok && connect(notifier, &LECBManagerNotifier::servicesWereModified,
949 this, &QLowEnergyControllerPrivateDarwin::_q_servicesWereModified);
950 ok = ok && connect(notifier, &LECBManagerNotifier::serviceDetailsDiscoveryFinished,
951 this, &QLowEnergyControllerPrivateDarwin::_q_serviceDetailsDiscoveryFinished);
952 ok = ok && connect(notifier, &LECBManagerNotifier::characteristicRead,
953 this, &QLowEnergyControllerPrivateDarwin::_q_characteristicRead);
954 ok = ok && connect(notifier, &LECBManagerNotifier::characteristicWritten,
955 this, &QLowEnergyControllerPrivateDarwin::_q_characteristicWritten);
956 ok = ok && connect(notifier, &LECBManagerNotifier::characteristicUpdated,
957 this, &QLowEnergyControllerPrivateDarwin::_q_characteristicUpdated);
958 ok = ok && connect(notifier, &LECBManagerNotifier::descriptorRead,
959 this, &QLowEnergyControllerPrivateDarwin::_q_descriptorRead);
960 ok = ok && connect(notifier, &LECBManagerNotifier::descriptorWritten,
961 this, &QLowEnergyControllerPrivateDarwin::_q_descriptorWritten);
962 ok = ok && connect(notifier, &LECBManagerNotifier::notificationEnabled,
963 this, &QLowEnergyControllerPrivateDarwin::_q_notificationEnabled);
964 ok = ok && connect(notifier, &LECBManagerNotifier::LEnotSupported,
965 this, &QLowEnergyControllerPrivateDarwin::_q_LEnotSupported);
966 ok = ok && connect(notifier, SIGNAL(CBManagerError(QLowEnergyController::Error)),
967 this, SLOT(_q_CBManagerError(QLowEnergyController::Error)));
968 ok = ok && connect(notifier, SIGNAL(CBManagerError(const QBluetoothUuid &, QLowEnergyController::Error)),
969 this, SLOT(_q_CBManagerError(const QBluetoothUuid &, QLowEnergyController::Error)));
970 ok = ok && connect(notifier, SIGNAL(CBManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError)),
971 this, SLOT(_q_CBManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError)));
972 ok = ok && connect(notifier, &LECBManagerNotifier::mtuChanged, this,
973 &QLowEnergyControllerPrivateDarwin::_q_mtuChanged);
974 ok = ok && connect(notifier, &LECBManagerNotifier::rssiUpdated, q_ptr,
975 &QLowEnergyController::rssiRead, Qt::QueuedConnection);
976
977 if (!ok)
978 notifier->disconnect();
979
980 return ok;
981}
982
983void QLowEnergyControllerPrivateDarwin::startAdvertising(const QLowEnergyAdvertisingParameters &params,
984 const QLowEnergyAdvertisingData &advertisingData,
985 const QLowEnergyAdvertisingData &scanResponseData)
986{
987 if (!lazyInit()) // Error was emit already.
988 return;
989
990 auto leQueue(DarwinBluetooth::qt_LE_queue());
991 Q_ASSERT_X(leQueue, Q_FUNC_INFO, "invalid LE queue (nullptr)");
992
993 const auto manager = peripheralManager.getAs<DarwinBTPeripheralManager>();
994 [manager setParameters:params data:advertisingData scanResponse:scanResponseData];
995
996 setState(QLowEnergyController::AdvertisingState);
997
998 dispatch_async(leQueue, ^{
999 [manager startAdvertising];
1000 });
1001}
1002
1003void QLowEnergyControllerPrivateDarwin::stopAdvertising()
1004{
1005 if (!isValid())
1006 return _q_CBManagerError(QLowEnergyController::UnknownError);
1007
1008 if (state != QLowEnergyController::AdvertisingState) {
1009 qCDebug(QT_BT_DARWIN) << "cannot stop advertising, called in state" << state;
1010 return;
1011 }
1012
1013 const auto leQueue = DarwinBluetooth::qt_LE_queue();
1014 Q_ASSERT_X(leQueue, Q_FUNC_INFO, "invalid LE queue (nullptr)");
1015 const auto manager = peripheralManager.getAs<DarwinBTPeripheralManager>();
1016 dispatch_sync(leQueue, ^{
1017 [manager stopAdvertising];
1018 });
1019
1020 setState(QLowEnergyController::UnconnectedState);
1021}
1022
1023QT_END_NAMESPACE
#define QT_BT_MAC_AUTORELEASEPOOL
Definition btutility_p.h:78