Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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
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
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
70{
72
73 if (!services || !services.count)
74 return UUIDList();
75
76 UUIDList uuids;
77
78 for (CBService *s in services)
80
81 return uuids;
82}
83
84} // unnamed namespace
85
87{
90 qRegisterMetaType<QLowEnergyHandle>("QLowEnergyHandle");
91 qRegisterMetaType<QSharedPointer<QLowEnergyServicePrivate>>();
92}
93
95{
96 if (const auto leQueue = DarwinBluetooth::qt_LE_queue()) {
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
112{
113 return centralManager || peripheralManager;
114}
115
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
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.");
134 return false;
135 }
136
137 std::unique_ptr<LECBManagerNotifier> notifier = std::make_unique<LECBManagerNotifier>();
139 peripheralManager.reset([[DarwinBTPeripheralManager alloc] initWith:notifier.get()],
141 Q_ASSERT(peripheralManager);
142 } else {
143 centralManager.reset([[DarwinBTCentralManager alloc] initWith:notifier.get()],
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
159{
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.
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
180
181 const QBluetoothUuid deviceUuidCopy(deviceUuid);
183 dispatch_async(leQueue, ^{
184 [manager connectToDevice:deviceUuidCopy];
185 });
186}
187
189{
190 Q_ASSERT(isValid()); // Check for proper state is in Qt's code.
191
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()) {
203
204 auto manager = centralManager.getAs<DarwinBTCentralManager>();
205 dispatch_async(leQueue, ^{
207 });
208
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.
215 }
216 } else {
217 qCCritical(QT_BT_DARWIN) << "qt LE queue is nil, "
218 "can not dispatch 'disconnect'";
219 }
220}
221
223{
225 Q_FUNC_INFO, "not connected to peripheral");
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
235
237 dispatch_async(leQueue, ^{
239 });
240}
241
244{
245 Q_UNUSED(mode);
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));
262 // Copy objects ...
264 const QBluetoothUuid serviceUuidCopy(serviceUuid);
265 dispatch_async(leQueue, ^{
267 });
268}
269
271{
273 // TODO: implement this, if possible.
274 qCWarning(QT_BT_DARWIN) << "Connection update not implemented on your platform";
275}
276
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
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
319
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>();
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{
346}
347
348void QLowEnergyControllerPrivateDarwin::_q_disconnected()
349{
352
355}
356
357void QLowEnergyControllerPrivateDarwin::_q_mtuChanged(int newValue)
358{
359 emit q_ptr->mtuChanged(newValue);
360}
361
362void QLowEnergyControllerPrivateDarwin::_q_serviceDiscoveryFinished()
363{
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
445
446 for (auto it = serviceList.constBegin(); it != serviceList.constEnd(); ++it) {
448 Q_ARG(QBluetoothUuid, it.key()));
449 }
450
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
474}
475
476void QLowEnergyControllerPrivateDarwin::_q_servicesWereModified()
477{
480 qCWarning(QT_BT_DARWIN) << "services were modified while controller is not in Discovered/Discovering state";
481 return;
482 }
483
486
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
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
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
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
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 =
620 if (!qtDescriptor.isValid()) {
621 qCWarning(QT_BT_DARWIN) << "characteristic" << charHandle
622 << "does not have a client characteristic "
623 "descriptor";
624 return;
625 }
626
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
660 // An error occurred during service discovery, finish the discovery.
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,
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,
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
705 qCWarning(QT_BT_DARWIN) << "invalid role (peripheral)";
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();
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
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
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.
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,
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);
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
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
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);
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
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);
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{
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);
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)));
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,
976
977 if (!ok)
979
980 return ok;
981}
982
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
997
998 dispatch_async(leQueue, ^{
1000 });
1001}
1002
1004{
1005 if (!isValid())
1006 return _q_CBManagerError(QLowEnergyController::UnknownError);
1007
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, ^{
1018 });
1019
1021}
1022
1024
CBPeripheral * peripheral
DarwinBluetooth::LECBManagerNotifier * notifier
std::vector< ObjCStrongReference< CBMutableService > > services
#define QT_BT_MAC_AUTORELEASEPOOL
Definition btutility_p.h:78
ObjCType * getAs() const
Definition btraii_p.h:67
Access Bluetooth peripherals.
\inmodule QtBluetooth
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
void append(parameter_type t)
Definition qlist.h:458
The QLowEnergyAdvertisingData class represents the data to be broadcast during Bluetooth Low Energy a...
The QLowEnergyAdvertisingParameters class represents the parameters used for Bluetooth Low Energy adv...
The QLowEnergyConnectionParameters class is used when requesting or reporting an update of the parame...
void writeDescriptor(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QLowEnergyHandle descriptorHandle, const QByteArray &newValue) override
void readDescriptor(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QLowEnergyHandle descriptorHandle) override
void startAdvertising(const QLowEnergyAdvertisingParameters &params, const QLowEnergyAdvertisingData &advertisingData, const QLowEnergyAdvertisingData &scanResponseData) override
void writeCharacteristic(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QByteArray &newValue, QLowEnergyService::WriteMode mode) override
void discoverServiceDetails(const QBluetoothUuid &serviceUuid, QLowEnergyService::DiscoveryMode mode) override
QLowEnergyService * addServiceHelper(const QLowEnergyServiceData &service) override
void addToGenericAttributeList(const QLowEnergyServiceData &service, QLowEnergyHandle startHandle) override
void requestConnectionUpdate(const QLowEnergyConnectionParameters &params) override
void readCharacteristic(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle) override
QLowEnergyCharacteristic characteristicForHandle(QLowEnergyHandle handle)
Returns a valid characteristic if the given handle is the handle of the characteristic itself or one ...
QSharedPointer< QLowEnergyServicePrivate > serviceForHandle(QLowEnergyHandle handle)
QLowEnergyDescriptor descriptorForHandle(QLowEnergyHandle handle)
Returns a valid descriptor if handle belongs to a descriptor; otherwise an invalid one.
void setError(QLowEnergyController::Error newError)
QLowEnergyController::ControllerState state
void setState(QLowEnergyController::ControllerState newState)
ControllerState
Indicates the state of the controller object.
void connected()
This signal is emitted when the controller successfully connects to the remote Low Energy device (if ...
void rssiRead(qint16 rssi)
This signal is emitted after successful read of RSSI (received signal strength indicator) for a conne...
Error
Indicates all possible error conditions found during the controller's existence.
void disconnected()
This signal is emitted when the controller disconnects from the remote Low Energy device or vice vers...
void discoverServices()
Initiates the service discovery process.
void discoveryFinished()
This signal is emitted when the running service discovery finishes.
void mtuChanged(int mtu)
This signal is emitted as a result of a successful MTU change.
\inmodule QtBluetooth
The QLowEnergyServiceData class is used to set up GATT service data. \inmodule QtBluetooth.
\inmodule QtBluetooth
DiscoveryMode
This enum lists service discovery modes.
ServiceError
This enum describes all possible error conditions during the service's existence.
WriteMode
This enum describes the mode to be used when writing a characteristic value.
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)
\threadsafe
Definition qobject.cpp:3236
const_iterator constBegin() const noexcept
Definition qset.h:139
const_iterator constEnd() const noexcept
Definition qset.h:143
bool isNull() const noexcept
Returns true if this is the null UUID {00000000-0000-0000-0000-000000000000}; otherwise returns false...
Definition quuid.cpp:818
QSet< QString >::iterator it
QBluetoothUuid qt_uuid(NSUUID *nsUuid)
dispatch_queue_t qt_LE_queue()
Definition btutility.mm:324
const int defaultMtu
Definition btutility.mm:34
Combined button and popup list for selecting options.
ServicePrivate qt_createLEService(QLowEnergyControllerPrivateDarwin *controller, CBService *cbService, bool included)
QSharedPointer< QLowEnergyServicePrivate > ServicePrivate
UUIDList qt_servicesUuids(NSArray *services)
Q_CORE_EXPORT QtJniTypes::Service service()
@ QueuedConnection
quint16 QLowEnergyHandle
Definition qbluetooth.h:42
unsigned long NSUInteger
#define Q_FUNC_INFO
#define qApp
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qCCritical(category,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
void registerQLowEnergyControllerMetaType()
#define SLOT(a)
Definition qobjectdefs.h:52
#define Q_ARG(Type, data)
Definition qobjectdefs.h:63
#define SIGNAL(a)
Definition qobjectdefs.h:53
GLenum mode
GLenum GLenum GLsizei count
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLenum GLsizei const GLuint GLboolean enabled
void ** params
GLdouble s
[6]
Definition qopenglext.h:235
GLuint in
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
#define emit
#define Q_UNUSED(x)
unsigned short quint16
Definition qtypes.h:48
gzip write("uncompressed data")
QNetworkAccessManager manager
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...