Warning: That file was not part of the compilation database. It may have many parsing errors.

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

Warning: That file was not part of the compilation database. It may have many parsing errors.