summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTimur Pocheptsov <timur.pocheptsov@theqtcompany.com>2016-02-07 13:02:34 +0100
committerTimur Pocheptsov <timur.pocheptsov@theqtcompany.com>2016-03-13 11:56:46 +0000
commitf51643a314af54400301bcb687829e258a857ac3 (patch)
tree911926af78e9e0db0e28a6c9a66aed8db8d1cf71
parent51e32a9f7972e31e65bccf36caa238ac245091e0 (diff)
Add peripheral role (iOS/OS X).
CoreBluetooth has CBPeripheralManager/CBMutableService both on iOS (since 6.0) and OS X (>= 10.9). This lets me implement the Qt's BTLE 'advertisement' API and peripheral role for both iOS and OS X. Change-Id: I3e69a5870535a45bc16bbd9862ca84300b01daca Reviewed-by: Timur Pocheptsov <timur.pocheptsov@theqtcompany.com>
-rw-r--r--src/bluetooth/osx/osxbt.pri13
-rw-r--r--src/bluetooth/osx/osxbtcentralmanager.mm54
-rw-r--r--src/bluetooth/osx/osxbtcentralmanager_p.h6
-rw-r--r--src/bluetooth/osx/osxbtnotifier_p.h8
-rw-r--r--src/bluetooth/osx/osxbtperipheralmanager.mm754
-rw-r--r--src/bluetooth/osx/osxbtperipheralmanager_p.h176
-rw-r--r--src/bluetooth/osx/osxbtutility.mm1
-rw-r--r--src/bluetooth/osx/osxbtutility_p.h4
-rw-r--r--src/bluetooth/qlowenergycontroller_osx.mm354
-rw-r--r--src/bluetooth/qlowenergycontroller_osx_p.h26
-rw-r--r--src/bluetooth/qlowenergyservice_osx.mm6
11 files changed, 1243 insertions, 159 deletions
diff --git a/src/bluetooth/osx/osxbt.pri b/src/bluetooth/osx/osxbt.pri
index bb382866..fc0a5a65 100644
--- a/src/bluetooth/osx/osxbt.pri
+++ b/src/bluetooth/osx/osxbt.pri
@@ -1,5 +1,6 @@
SOURCES += osx/uistrings.cpp osx/osxbtnotifier.cpp
PRIVATE_HEADERS += osx/uistrings_p.h
+//QMAKE_CXXFLAGS_WARN_ON += -Wno-nullability-completeness
CONFIG(osx) {
PRIVATE_HEADERS += osx/osxbtutility_p.h \
@@ -16,7 +17,8 @@ CONFIG(osx) {
osx/osxbtledeviceinquiry_p.h \
osx/corebluetoothwrapper_p.h \
osx/osxbtcentralmanager_p.h \
- osx/osxbtnotifier_p.h
+ osx/osxbtnotifier_p.h \
+ osx/osxbtperipheralmanager_p.h
OBJECTIVE_SOURCES += osx/osxbtutility.mm \
osx/osxbtdevicepair.mm \
@@ -30,15 +32,18 @@ CONFIG(osx) {
osx/osxbtsocketlistener.mm \
osx/osxbtobexsession.mm \
osx/osxbtledeviceinquiry.mm \
- osx/osxbtcentralmanager.mm
+ osx/osxbtcentralmanager.mm \
+ osx/osxbtperipheralmanager.mm
} else {
PRIVATE_HEADERS += osx/osxbtutility_p.h \
osx/osxbtledeviceinquiry_p.h \
osx/corebluetoothwrapper_p.h \
osx/osxbtcentralmanager_p.h \
- osx/osxbtnotifier_p.h
+ osx/osxbtnotifier_p.h \
+ osx/osxbtperipheralmanager_p.h
OBJECTIVE_SOURCES += osx/osxbtutility.mm \
osx/osxbtledeviceinquiry.mm \
- osx/osxbtcentralmanager.mm
+ osx/osxbtcentralmanager.mm \
+ osx/osxbtperipheralmanager.mm
}
diff --git a/src/bluetooth/osx/osxbtcentralmanager.mm b/src/bluetooth/osx/osxbtcentralmanager.mm
index 9e0451d2..2d267e4f 100644
--- a/src/bluetooth/osx/osxbtcentralmanager.mm
+++ b/src/bluetooth/osx/osxbtcentralmanager.mm
@@ -116,7 +116,7 @@ QT_END_NAMESPACE
@implementation QT_MANGLE_NAMESPACE(OSXBTCentralManager)
-- (id)initWith:(OSXBluetooth::LECentralNotifier *)aNotifier
+- (id)initWith:(OSXBluetooth::LECBManagerNotifier *)aNotifier
{
if (self = [super init]) {
manager = nil;
@@ -173,7 +173,7 @@ QT_END_NAMESPACE
managerState = OSXBluetooth::CentralManagerIdle;
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate a central manager";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError);
+ emit notifier->CBManagerError(QLowEnergyController::ConnectionError);
}
} else if (managerState != OSXBluetooth::CentralManagerUpdating) {
[self retrievePeripheralAndConnect];
@@ -205,7 +205,7 @@ QT_END_NAMESPACE
if (!uuids) {
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate identifiers";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError);
+ emit notifier->CBManagerError(QLowEnergyController::ConnectionError);
return;
}
@@ -219,7 +219,7 @@ QT_END_NAMESPACE
if (!nsUuid) {
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate NSUUID identifier";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError);
+ emit notifier->CBManagerError(QLowEnergyController::ConnectionError);
return;
}
@@ -230,7 +230,7 @@ QT_END_NAMESPACE
if (!peripherals || peripherals.count != 1) {
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to retrive a peripheral";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError);
+ emit notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
return;
}
@@ -243,7 +243,7 @@ QT_END_NAMESPACE
if (![manager respondsToSelector:@selector(retrievePeripherals:)]) {
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to retrive a peripheral";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError);
+ emit notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
return;
}
@@ -251,7 +251,7 @@ QT_END_NAMESPACE
if (!cfUuid) {
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to create CFUUID object";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError);
+ emit notifier->CBManagerError(QLowEnergyController::ConnectionError);
return;
}
// With ARC this cast will be illegal:
@@ -406,7 +406,7 @@ QT_END_NAMESPACE
<< serviceUuid;
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::UnknownError);
}
}
@@ -501,7 +501,7 @@ QT_END_NAMESPACE
// Well, that's unlikely :) But we must be sure.
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "can not allocate more handles";
if (notifier)
- notifier->CBCentralManagerError(serviceUuid, QLowEnergyService::OperationError);
+ notifier->CBManagerError(serviceUuid, QLowEnergyService::OperationError);
return;
}
@@ -721,7 +721,7 @@ QT_END_NAMESPACE
<< "unknown characteristic handle "
<< charHandle;
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::DescriptorWriteError);
}
return;
@@ -735,7 +735,7 @@ QT_END_NAMESPACE
qCWarning(QT_BT_OSX) << Q_FUNC_INFO
<< "no client characteristic configuration found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::DescriptorWriteError);
}
return;
@@ -762,7 +762,7 @@ QT_END_NAMESPACE
if (!charMap.contains(charHandle)) {
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "characteristic: " << charHandle << " not found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::CharacteristicReadError);
}
@@ -792,7 +792,7 @@ QT_END_NAMESPACE
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "characteristic: "
<< charHandle << " not found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::CharacteristicWriteError);
}
return;
@@ -819,7 +819,7 @@ QT_END_NAMESPACE
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "handle:"
<< descHandle << "not found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::DescriptorReadError);
}
return;
@@ -845,7 +845,7 @@ QT_END_NAMESPACE
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "handle: "
<< descHandle << " not found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::DescriptorWriteError);
}
return;
@@ -1101,7 +1101,7 @@ QT_END_NAMESPACE
// and reset managerState from CentralManagerUpdating.
managerState = CentralManagerIdle;
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::InvalidBluetoothAdapterError);
+ emit notifier->CBManagerError(QLowEnergyController::InvalidBluetoothAdapterError);
}
return;
}
@@ -1116,7 +1116,7 @@ QT_END_NAMESPACE
// TODO: we need a better error +
// what will happen if later the state changes to PoweredOn???
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::InvalidBluetoothAdapterError);
+ emit notifier->CBManagerError(QLowEnergyController::InvalidBluetoothAdapterError);
}
return;
}
@@ -1149,7 +1149,7 @@ QT_END_NAMESPACE
if (!peripherals || peripherals.count != 1) {
qCDebug(QT_BT_OSX) << Q_FUNC_INFO <<"unexpected number of peripherals (!= 1)";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError);
+ emit notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
} else {
peripheral = [static_cast<CBPeripheral *>([peripherals objectAtIndex:0]) retain];
[self connectToPeripheral];
@@ -1186,7 +1186,7 @@ QT_END_NAMESPACE
managerState = OSXBluetooth::CentralManagerIdle;
// TODO: better error mapping is required.
if (notifier)
- notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError);
+ notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
}
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)aPeripheral
@@ -1202,7 +1202,7 @@ QT_END_NAMESPACE
managerState = OSXBluetooth::CentralManagerIdle;
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to disconnect";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError);
+ emit notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
} else {
managerState = OSXBluetooth::CentralManagerIdle;
if (notifier)
@@ -1227,7 +1227,7 @@ QT_END_NAMESPACE
NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
// TODO: better error mapping required.
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::UnknownError);
+ emit notifier->CBManagerError(QLowEnergyController::UnknownError);
} else {
[self discoverIncludedServices];
}
@@ -1323,7 +1323,7 @@ QT_END_NAMESPACE
NSLog(@"%s failed with error: %@", Q_FUNC_INFO, error);
// We did not discover any characteristics and can not discover descriptors,
// inform our delegate (it will set a service state also).
- emit notifier->CBCentralManagerError(qt_uuid(service.UUID), QLowEnergyController::UnknownError);
+ emit notifier->CBManagerError(qt_uuid(service.UUID), QLowEnergyController::UnknownError);
} else {
[self readCharacteristics:service];
}
@@ -1358,7 +1358,7 @@ QT_END_NAMESPACE
if (chHandle && chHandle == currentReadHandle) {
currentReadHandle = 0;
requestPending = false;
- emit notifier->CBCentralManagerError(qtUuid, QLowEnergyService::CharacteristicReadError);
+ emit notifier->CBManagerError(qtUuid, QLowEnergyService::CharacteristicReadError);
[self performNextRequest];
}
return;
@@ -1460,7 +1460,7 @@ QT_END_NAMESPACE
if (dHandle && dHandle == currentReadHandle) {
currentReadHandle = 0;
requestPending = false;
- emit notifier->CBCentralManagerError(qtUuid, QLowEnergyService::DescriptorReadError);
+ emit notifier->CBManagerError(qtUuid, QLowEnergyService::DescriptorReadError);
[self performNextRequest];
}
return;
@@ -1541,7 +1541,7 @@ QT_END_NAMESPACE
if (error) {
NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
- emit notifier->CBCentralManagerError(qt_uuid(characteristic.service.UUID),
+ emit notifier->CBManagerError(qt_uuid(characteristic.service.UUID),
QLowEnergyService::CharacteristicWriteError);
} else {
const QLowEnergyHandle cHandle = charMap.key(characteristic);
@@ -1575,7 +1575,7 @@ QT_END_NAMESPACE
if (error) {
NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
- emit notifier->CBCentralManagerError(qt_uuid(descriptor.characteristic.service.UUID),
+ emit notifier->CBManagerError(qt_uuid(descriptor.characteristic.service.UUID),
QLowEnergyService::DescriptorWriteError);
} else {
const QLowEnergyHandle dHandle = descMap.key(descriptor);
@@ -1610,7 +1610,7 @@ QT_END_NAMESPACE
if (error) {
NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
// In Qt's API it's a descriptor write actually.
- emit notifier->CBCentralManagerError(qt_uuid(characteristic.service.UUID),
+ emit notifier->CBManagerError(qt_uuid(characteristic.service.UUID),
QLowEnergyService::DescriptorWriteError);
} else if (nRemoved) {
const QLowEnergyHandle dHandle = descMap.key(descriptor);
diff --git a/src/bluetooth/osx/osxbtcentralmanager_p.h b/src/bluetooth/osx/osxbtcentralmanager_p.h
index e64e5baf..5453822f 100644
--- a/src/bluetooth/osx/osxbtcentralmanager_p.h
+++ b/src/bluetooth/osx/osxbtcentralmanager_p.h
@@ -75,7 +75,7 @@ class QLowEnergyServicePrivate;
namespace OSXBluetooth {
-class LECentralNotifier;
+class LECBManagerNotifier;
enum CentralManagerState
{
@@ -144,7 +144,7 @@ QT_END_NAMESPACE
QT_PREPEND_NAMESPACE(QBluetoothUuid) deviceUuid;
- QT_PREPEND_NAMESPACE(OSXBluetooth)::LECentralNotifier *notifier;
+ QT_PREPEND_NAMESPACE(OSXBluetooth)::LECBManagerNotifier *notifier;
// Quite a verbose service discovery machinery
// (a "graph traversal").
@@ -173,7 +173,7 @@ QT_END_NAMESPACE
CBPeripheral *peripheral;
}
-- (id)initWith:(QT_PREPEND_NAMESPACE(OSXBluetooth)::LECentralNotifier *)notifier;
+- (id)initWith:(QT_PREPEND_NAMESPACE(OSXBluetooth)::LECBManagerNotifier *)notifier;
- (void)dealloc;
// IMPORTANT: _all_ these methods are to be executed on qt_LE_queue,
diff --git a/src/bluetooth/osx/osxbtnotifier_p.h b/src/bluetooth/osx/osxbtnotifier_p.h
index 6cb2b019..3059e2d3 100644
--- a/src/bluetooth/osx/osxbtnotifier_p.h
+++ b/src/bluetooth/osx/osxbtnotifier_p.h
@@ -68,7 +68,7 @@ class QLowEnergyServicePrivate;
namespace OSXBluetooth
{
-class LECentralNotifier : public QObject
+class LECBManagerNotifier : public QObject
{
Q_OBJECT
@@ -85,9 +85,9 @@ Q_SIGNALS:
void descriptorWritten(QLowEnergyHandle descHandle, const QByteArray &value);
void LEnotSupported();
- void CBCentralManagerError(QLowEnergyController::Error error);
- void CBCentralManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyController::Error error);
- void CBCentralManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error);
+ void CBManagerError(QLowEnergyController::Error error);
+ void CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyController::Error error);
+ void CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error);
};
diff --git a/src/bluetooth/osx/osxbtperipheralmanager.mm b/src/bluetooth/osx/osxbtperipheralmanager.mm
new file mode 100644
index 00000000..4731fdd2
--- /dev/null
+++ b/src/bluetooth/osx/osxbtperipheralmanager.mm
@@ -0,0 +1,754 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+
+#include "qlowenergycharacteristicdata.h"
+#include "qlowenergydescriptordata.h"
+#include "osxbtperipheralmanager_p.h"
+#include "qlowenergyservicedata.h"
+#include "osxbtnotifier_p.h"
+#include "qbluetooth.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qlist.h>
+
+#include <algorithm>
+#include <limits>
+#include <set>
+
+namespace
+{
+
+CBCharacteristicProperties cb_properties(const QLowEnergyCharacteristicData &data)
+{
+ // Direct 'mapping' is ok.
+ return CBCharacteristicProperties(int(data.properties()));
+}
+
+CBAttributePermissions cb_permissions(const QLowEnergyCharacteristicData &data)
+{
+ using QLEC = QLowEnergyCharacteristic;
+
+ const auto props = data.properties();
+ CBAttributePermissions cbFlags = {};
+
+ if ((props & QLEC::Write) || (props & QLEC::WriteNoResponse)
+ || (props & QLEC::WriteSigned)) {
+ cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsWriteable);
+ }
+
+ if (props & QLEC::Read)
+ cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsReadable);
+
+ if (data.writeConstraints() & QBluetooth::AttEncryptionRequired)
+ cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsWriteEncryptionRequired);
+
+ if (data.readConstraints() & QBluetooth::AttEncryptionRequired)
+ cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsReadEncryptionRequired);
+
+ return cbFlags;
+}
+
+ObjCStrongReference<CBMutableCharacteristic> create_characteristic(const QLowEnergyCharacteristicData &data)
+{
+ const ObjCStrongReference<CBMutableCharacteristic> ch([[CBMutableCharacteristic alloc] initWithType:cb_uuid(data.uuid())
+ properties:cb_properties(data)
+ value:nil
+ permissions:cb_permissions(data)],
+ false /*do not retain*/);
+ return ch;
+}
+
+ObjCStrongReference<CBMutableDescriptor> create_descriptor(const QLowEnergyDescriptorData &data)
+{
+ // CoreBluetooth supports only:
+ /*
+ "That said, only two of these are currently supported when creating local,
+ mutable descriptors: the characteristic user description descriptor and
+ the characteristic format descriptor, represented by the CBUUID constants
+ CBUUIDCharacteristicUserDescriptionString and CBUUIDCharacteristicFormatString"
+ */
+
+ if (data.uuid() != QBluetoothUuid::CharacteristicUserDescription &&
+ data.uuid() != QBluetoothUuid::CharacteristicPresentationFormat) {
+ qCWarning(QT_BT_OSX) << "unsupported descriptor" << data.uuid();
+ return {};
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ // Descriptors are immutable with CoreBluetooth, that's why we
+ // have to provide a value here and not able to change it later.
+ ObjCStrongReference<NSObject> value;
+ if (data.uuid() == QBluetoothUuid::CharacteristicUserDescription) {
+ const QString asQString(QString::fromUtf8(data.value()));
+ value.reset(asQString.toNSString());
+ } else {
+ const auto nsData = data_from_bytearray(data.value());
+ value.reset(nsData.data());
+ }
+
+ const ObjCStrongReference<CBMutableDescriptor> d([[CBMutableDescriptor alloc]
+ initWithType:cb_uuid(data.uuid())
+ value:value], false /*do not retain*/);
+ return d;
+}
+
+quint32 qt_countGATTEntries(const QLowEnergyServiceData &data)
+{
+ const auto maxu32 = std::numeric_limits<quint32>::max();
+ // + 1 for a service itself.
+ quint32 nEntries = 1 + quint32(data.includedServices().count());
+ for (const auto &ch : data.characteristics()) {
+ if (maxu32 - 2 < nEntries)
+ return {};
+ nEntries += 2;
+ if (maxu32 - ch.descriptors().count() < nEntries)
+ return {};
+ nEntries += ch.descriptors().count();
+ }
+
+ return nEntries;
+}
+
+}
+
+@interface QT_MANGLE_NAMESPACE(OSXBTPeripheralManager) (PrivateAPI)
+
+- (void)addConnectedCentral:(CBCentral *)central;
+- (void)removeConnectedCentral:(CBCentral *)central;
+- (CBService *)findIncludedService:(const QBluetoothUuid &)qtUUID;
+
+- (void)addIncludedServices:(const QLowEnergyServiceData &)data
+ to:(CBMutableService *)cbService
+ qtService:(QLowEnergyServicePrivate *)qtService;
+
+- (void)addCharacteristicsAndDescriptors:(const QLowEnergyServiceData &)data
+ to:(CBMutableService *)cbService
+ qtService:(QLowEnergyServicePrivate *)qtService;
+
+- (CBATTError)validateWriteRequest:(CBATTRequest *)request;
+
+@end
+
+@implementation QT_MANGLE_NAMESPACE(OSXBTPeripheralManager)
+
+- (id)initWith:(LECBManagerNotifier *)aNotifier
+{
+ if (self = [super init]) {
+ Q_ASSERT(aNotifier);
+ notifier = aNotifier;
+ state = PeripheralState::idle;
+ nextServiceToAdd = {};
+ connectedCentrals.reset([[NSMutableSet alloc] init]);
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [self detach];
+ [super dealloc];
+}
+
+- (QSharedPointer<QLowEnergyServicePrivate>)addService:(const QLowEnergyServiceData &)data
+{
+ using QLES = QLowEnergyService;
+
+ const auto nEntries = qt_countGATTEntries(data);
+ if (!nEntries || nEntries > std::numeric_limits<QLowEnergyHandle>::max() - lastHandle) {
+ qCCritical(QT_BT_OSX) << "addService: not enough handles";
+ return {};
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ const BOOL primary = data.type() == QLowEnergyServiceData::ServiceTypePrimary;
+ const auto cbUUID = cb_uuid(data.uuid());
+
+ const ObjCStrongReference<CBMutableService>
+ newCBService([[CBMutableService alloc] initWithType:cbUUID primary:primary],
+ false /*do not retain*/);
+
+ if (!newCBService) {
+ qCCritical(QT_BT_OSX) << "addService: failed to create CBMutableService";
+ return {};
+ }
+
+ auto newQtService = QSharedPointer<QLowEnergyServicePrivate>::create();
+ newQtService->state = QLowEnergyService::LocalService;
+ newQtService->uuid = data.uuid();
+ newQtService->type = primary ? QLES::PrimaryService : QLES::IncludedService;
+ newQtService->startHandle = ++lastHandle;
+ // Controller will be set by ... controller :)
+
+ [self addIncludedServices:data to:newCBService qtService:newQtService.data()];
+ [self addCharacteristicsAndDescriptors:data to:newCBService qtService:newQtService.data()];
+
+ services.push_back(newCBService);
+ serviceIndex[data.uuid()] = newCBService;
+
+ return newQtService;
+}
+
+- (void) setParameters:(const QLowEnergyAdvertisingParameters &)parameters
+ data:(const QLowEnergyAdvertisingData &)data
+ scanResponse:(const QLowEnergyAdvertisingData &)scanResponse
+{
+ Q_UNUSED(parameters)
+
+ // This is the last method we call on the controller's thread
+ // before starting advertising on the Qt's LE queue.
+ // From Apple's docs:
+ /*
+ - (void)startAdvertising:(NSDictionary *)advertisementData
+
+ Advertises peripheral manager data.
+
+ * advertisementData
+
+ - An optional dictionary containing the data you want to advertise.
+ The possible keys of an advertisementData dictionary are detailed in CBCentralManagerDelegate
+ Protocol Reference. That said, only two of the keys are supported for peripheral manager objects:
+ CBAdvertisementDataLocalNameKey and CBAdvertisementDataServiceUUIDsKey.
+ */
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ advertisementData.reset([[NSMutableDictionary alloc] init]);
+ if (!advertisementData) {
+ qCWarning(QT_BT_OSX) << "setParameters: failed to allocate "
+ "NSMutableDictonary (advertisementData)";
+ return;
+ }
+
+ auto localName = scanResponse.localName();
+ if (!localName.size())
+ localName = data.localName();
+
+ if (localName.size()) {
+ [advertisementData setObject:localName.toNSString()
+ forKey:CBAdvertisementDataLocalNameKey];
+ }
+
+ if (!data.services().count() && !scanResponse.services().count())
+ return;
+
+ const ObjCScopedPointer<NSMutableArray> uuids([[NSMutableArray alloc] init]);
+ if (!uuids) {
+ qCWarning(QT_BT_OSX) << "setParameters: failed to allocate "
+ "NSMutableArray (services uuids)";
+ return;
+ }
+
+
+ for (const auto &qtUUID : data.services()) {
+ const auto cbUUID = cb_uuid(qtUUID);
+ if (cbUUID)
+ [uuids addObject:cbUUID];
+ }
+
+ for (const auto &qtUUID : scanResponse.services()) {
+ const auto cbUUID = cb_uuid(qtUUID);
+ if (cbUUID)
+ [uuids addObject:cbUUID];
+ }
+
+ if ([uuids count]) {
+ [advertisementData setObject:uuids
+ forKey:CBAdvertisementDataServiceUUIDsKey];
+ }
+}
+
+- (void)startAdvertising
+{
+ state = PeripheralState::waitingForPowerOn;
+ if (manager)
+ [manager setDelegate:nil];
+ manager.reset([[CBPeripheralManager alloc] initWithDelegate:self
+ queue:OSXBluetooth::qt_LE_queue()]);
+}
+
+- (void)stopAdvertising
+{
+ [manager stopAdvertising];
+ state = PeripheralState::idle;
+}
+
+- (void)detach
+{
+ if (notifier) {
+ notifier->disconnect();
+ notifier->deleteLater();
+ notifier = nullptr;
+ }
+
+ if (state == PeripheralState::advertising) {
+ [manager stopAdvertising];
+ [manager setDelegate:nil];
+ state = PeripheralState::idle;
+ }
+}
+
+- (void)write:(const QByteArray &)value
+ charHandle:(QLowEnergyHandle)charHandle
+{
+ if (!notifier)
+ return;
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ if (!charMap.contains(charHandle)) {
+ emit notifier->CBManagerError(QLowEnergyController::UnknownError);
+ return;
+ }
+
+ const auto nsData = data_from_bytearray(value);
+ charValues[charHandle] = nsData;
+ updateQueue.push_back(UpdateRequest{charHandle, nsData});
+ [self sendUpdateRequests];
+}
+
+- (void) addServicesToPeripheral
+{
+ Q_ASSERT(manager);
+
+ if (nextServiceToAdd < services.size())
+ [manager addService:services[nextServiceToAdd++]];
+}
+
+// CBPeripheralManagerDelegate:
+
+- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
+{
+ if (peripheral != manager || !notifier)
+ return;
+
+ if (peripheral.state == CBPeripheralManagerStatePoweredOn) {
+ // "Bluetooth is currently powered on and is available to use."
+ if (state == PeripheralState::waitingForPowerOn) {
+ [manager removeAllServices];
+ nextServiceToAdd = {};
+ state = PeripheralState::advertising;
+ [self addServicesToPeripheral];
+ }
+ return;
+ }
+
+ /*
+ "A state with a value lower than CBPeripheralManagerStatePoweredOn implies that
+ advertising has stopped and that any connected centrals have been disconnected."
+ */
+
+ [connectedCentrals removeAllObjects];
+
+ if (state == PeripheralState::advertising) {
+ state = PeripheralState::waitingForPowerOn;
+ } else if (state == PeripheralState::connected) {
+ state = PeripheralState::idle;
+ emit notifier->disconnected();
+ }
+
+ // The next four states are _below_ "powered off"; according to the docs:
+ /*
+ "In addition, the local database is cleared and all services must be
+ explicitly added again."
+ */
+
+ if (peripheral.state == CBPeripheralManagerStateUnauthorized ||
+ peripheral.state == CBPeripheralManagerStateUnsupported) {
+ emit notifier->LEnotSupported();
+ state = PeripheralState::idle;
+ }
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ willRestoreState:(NSDictionary *)dict
+{
+ Q_UNUSED(peripheral)
+ Q_UNUSED(dict)
+ // NOOP atm.
+}
+
+- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
+ error:(NSError *)error
+{
+ if (peripheral != manager || !notifier)
+ return;
+
+ if (error) {
+ NSLog(@"failed to start advertising, error: %@", error);
+ state = PeripheralState::idle;
+ emit notifier->CBManagerError(QLowEnergyController::AdvertisingError);
+ }
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didAddService:(CBService *)service error:(NSError *)error
+{
+ Q_UNUSED(service)
+
+ if (peripheral != manager || !notifier)
+ return;
+
+ if (error) {
+ NSLog(@"failed to add a service, error: %@", error);
+ emit notifier->CBManagerError(QLowEnergyController::AdvertisingError);
+ state = PeripheralState::idle;
+ return;
+ }
+
+ if (nextServiceToAdd == services.size())
+ [manager startAdvertising:[advertisementData count] ? advertisementData.data() : nil];
+ else
+ [self addServicesToPeripheral];
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
+ didSubscribeToCharacteristic:(CBCharacteristic *)characteristic
+{
+ Q_UNUSED(characteristic)
+
+ if (peripheral != manager || !notifier)
+ return;
+
+ [self addConnectedCentral:central];
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
+ didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic
+{
+ Q_UNUSED(characteristic)
+
+ if (peripheral != manager || !notifier)
+ return;
+
+ [self removeConnectedCentral:central];
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didReceiveReadRequest:(CBATTRequest *)request
+{
+ if (peripheral != manager || !notifier)
+ return;
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ const auto handle = charMap.key(request.characteristic);
+ if (!handle || !charValues.contains(handle)) {
+ qCWarning(QT_BT_OSX) << "invalid read request, unknown characteristic";
+ [manager respondToRequest:request withResult:CBATTErrorInvalidHandle];
+ return;
+ }
+
+ const auto &value = charValues[handle];
+ if (request.offset > [value length]) {
+ qCWarning(QT_BT_OSX) << "invalid offset in a read request";
+ [manager respondToRequest:request withResult:CBATTErrorInvalidOffset];
+ return;
+ }
+
+ [self addConnectedCentral:request.central];
+
+ NSData *dataToSend = nil;
+ if (!request.offset) {
+ dataToSend = value;
+ } else {
+ dataToSend = [value subdataWithRange:
+ NSMakeRange(request.offset, [value length] - request.offset)];
+ }
+
+ request.value = dataToSend;
+ [manager respondToRequest:request withResult:CBATTErrorSuccess];
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didReceiveWriteRequests:(NSArray *)requests
+{
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ if (peripheral != manager || !notifier) {
+ // Detached already.
+ return;
+ }
+
+ // We first test if all requests are valid
+ // since CoreBluetooth requires "all or none"
+ // and respond only _once_ to the first one.
+ for (CBATTRequest *request in requests) {
+ const auto status = [self validateWriteRequest:request];
+ if (status != CBATTErrorSuccess) {
+ [manager respondToRequest:[requests objectAtIndex:0]
+ withResult:status];
+ return;
+ }
+ }
+
+ std::set<QLowEnergyHandle> updated;
+
+ for (CBATTRequest *request in requests) {
+ // Transition to 'connected' if needed.
+ [self addConnectedCentral:request.central];
+
+ const auto charHandle = charMap.key(request.characteristic);
+ updated.insert(charHandle);
+ NSMutableData *const data = static_cast<NSMutableData *>(charValues[charHandle]);
+ [data replaceBytesInRange:NSMakeRange(request.offset, request.value.length)
+ withBytes:data.bytes];
+ }
+
+ for (const auto handle : updated)
+ emit notifier->characteristicUpdated(handle, qt_bytearray(charValues[handle]));
+
+ if (requests.count) {
+ [manager respondToRequest:[requests objectAtIndex:0]
+ withResult:CBATTErrorSuccess];
+ }
+}
+
+- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral
+{
+ if (peripheral != manager || !notifier) {
+ // Detached.
+ return;
+ }
+
+ [self sendUpdateRequests];
+}
+
+- (void)sendUpdateRequests
+{
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ while (updateQueue.size()) {
+ const auto &request = updateQueue.front();
+ Q_ASSERT(charMap.contains(request.charHandle));
+ const BOOL res = [manager updateValue:request.value
+ forCharacteristic:static_cast<CBMutableCharacteristic *>(charMap[request.charHandle])
+ onSubscribedCentrals:nil];
+ if (!res) {
+ // Have to wait for the 'ManagerIsReadyToUpdate'.
+ break;
+ }
+
+ updateQueue.pop_front();
+ }
+}
+
+// Private API:
+
+- (void)addConnectedCentral:(CBCentral *)central
+{
+ if (!central)
+ return;
+
+ if (!notifier) {
+ // We were detached.
+ return;
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ if (state == PeripheralState::advertising) {
+ state = PeripheralState::connected;
+ [manager stopAdvertising];
+ emit notifier->connected();
+ }
+
+ if (![connectedCentrals containsObject:central.identifier])
+ [connectedCentrals addObject:central.identifier];
+}
+
+- (void)removeConnectedCentral:(CBCentral *)central
+{
+ if (!notifier) {
+ // Detached.
+ return;
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ if ([connectedCentrals containsObject:central.identifier])
+ [connectedCentrals removeObject:central.identifier];
+
+ if (state == PeripheralState::connected && ![connectedCentrals count]) {
+ state = PeripheralState::idle;
+ emit notifier->disconnected();
+ }
+}
+
+- (CBService *)findIncludedService:(const QBluetoothUuid &)qtUUID
+{
+ const auto it = serviceIndex.find(qtUUID);
+ if (it == serviceIndex.end())
+ return nil;
+
+ return it->second;
+}
+
+- (void)addIncludedServices:(const QLowEnergyServiceData &)data
+ to:(CBMutableService *)cbService
+ qtService:(QLowEnergyServicePrivate *)qtService
+{
+ Q_ASSERT(cbService);
+ Q_ASSERT(qtService);
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ ObjCScopedPointer<NSMutableArray> included([[NSMutableArray alloc] init]);
+ if (!included) {
+ qCWarning(QT_BT_OSX) << "addIncludedSerivces: failed "
+ "to allocate NSMutableArray";
+ return;
+ }
+
+ for (auto includedService : data.includedServices()) {
+ if (CBService *cbs = [self findIncludedService:includedService->serviceUuid()]) {
+ [included addObject:cbs];
+ qtService->includedServices << includedService->serviceUuid();
+ ++lastHandle;
+ } else {
+ qCWarning(QT_BT_OSX) << "can not use" << includedService->serviceUuid()
+ << "as included, it has to be added first";
+ }
+ }
+
+ if ([included count])
+ cbService.includedServices = included;
+}
+
+- (void)addCharacteristicsAndDescriptors:(const QLowEnergyServiceData &)data
+ to:(CBMutableService *)cbService
+ qtService:(QLowEnergyServicePrivate *)qtService
+{
+ Q_ASSERT(cbService);
+ Q_ASSERT(qtService);
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ ObjCScopedPointer<NSMutableArray> newCBChars([[NSMutableArray alloc] init]);
+ if (!newCBChars) {
+ qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: "
+ "failed to allocate NSMutableArray "
+ "(characteristics)";
+ return;
+ }
+
+ for (const auto &ch : data.characteristics()) {
+ const auto cbChar(create_characteristic(ch));
+ if (!cbChar) {
+ qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: "
+ "failed to allocate a characteristic";
+ continue;
+ }
+
+ const auto nsData(data_from_bytearray(ch.value()));
+ if (!nsData) {
+ qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: "
+ "addService: failed to allocate NSData (char value)";
+ continue;
+ }
+
+ [newCBChars addObject:cbChar];
+
+ const auto declHandle = ++lastHandle;
+ // CB part:
+ charMap[declHandle] = cbChar;
+ charValues[declHandle] = data_from_bytearray(ch.value());
+ // QT part:
+ QLowEnergyServicePrivate::CharData charData;
+ charData.valueHandle = ++lastHandle;
+ charData.uuid = ch.uuid();
+ charData.properties = ch.properties();
+ charData.value = ch.value();
+
+ const ObjCScopedPointer<NSMutableArray> newCBDescs([[NSMutableArray alloc] init]);
+ if (!newCBDescs) {
+ qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: "
+ "failed to allocate NSMutableArray "
+ "(descriptors)";
+ continue;
+ }
+
+ for (const auto &desc : ch.descriptors()) {
+ // CB part:
+ const auto cbDesc(create_descriptor(desc));
+ const auto descHandle = ++lastHandle;
+ if (cbDesc) {
+ // See comments in create_descriptor on
+ // why cbDesc can be nil.
+ [newCBDescs addObject:cbDesc];
+ }
+ // QT part:
+ QLowEnergyServicePrivate::DescData descData;
+ descData.uuid = desc.uuid();
+ descData.value = desc.value();
+ charData.descriptorList.insert(descHandle, descData);
+ }
+
+ if ([newCBDescs count])
+ cbChar.data().descriptors = newCBDescs.data(); // retains
+
+ qtService->characteristicList.insert(declHandle, charData);
+ }
+
+ if ([newCBChars count])
+ cbService.characteristics = newCBChars.data();
+}
+
+- (CBATTError)validateWriteRequest:(CBATTRequest *)request
+{
+ Q_ASSERT(request);
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ const auto handle = charMap.key(request.characteristic);
+ if (!handle || !charValues.contains(handle))
+ return CBATTErrorInvalidHandle;
+
+ NSMutableData *data = static_cast<NSMutableData *>(charValues[handle]);
+ if (request.offset > data.length || request.value.length > data.length - request.offset)
+ return CBATTErrorInvalidOffset;
+
+ return CBATTErrorSuccess;
+}
+
+@end
diff --git a/src/bluetooth/osx/osxbtperipheralmanager_p.h b/src/bluetooth/osx/osxbtperipheralmanager_p.h
new file mode 100644
index 00000000..481a9fab
--- /dev/null
+++ b/src/bluetooth/osx/osxbtperipheralmanager_p.h
@@ -0,0 +1,176 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef OSXBTPERIPHERALMANAGER_P_H
+#define OSXBTPERIPHERALMANAGER_P_H
+
+
+#include "osxbtutility_p.h"
+
+#include "qlowenergyadvertisingparameters.h"
+#include "qlowenergyserviceprivate_p.h"
+#include "qbluetoothuuid.h"
+#include "qbluetooth.h"
+
+#include <QtCore/qsharedpointer.h>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qsysinfo.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qmap.h>
+
+#include <vector>
+#include <deque>
+#include <map>
+
+// Foundation.h must be included before corebluetoothwrapper_p.h -
+// a workaround for a broken 10.9 SDK.
+#include <Foundation/Foundation.h>
+
+#include "corebluetoothwrapper_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QLowEnergyServiceData;
+
+namespace OSXBluetooth
+{
+
+class LECBManagerNotifier;
+
+}
+
+QT_END_NAMESPACE
+
+
+// Exposing names in a header is ugly, but constant QT_PREPEND_NAMESPACE is even worse ...
+// After all, this header is to be included only in its own and controller's *.mm files.
+
+QT_USE_NAMESPACE
+
+using namespace OSXBluetooth;
+
+
+template<class Type>
+using GenericLEMap = QMap<QLowEnergyHandle, Type>;
+
+enum class PeripheralState
+{
+ idle,
+ waitingForPowerOn,
+ advertising,
+ connected
+};
+
+struct UpdateRequest
+{
+ UpdateRequest() = default;
+ UpdateRequest(QLowEnergyHandle handle, const ObjCStrongReference<NSData> &val)
+ : charHandle(handle),
+ value(val)
+ {
+ }
+
+ QLowEnergyHandle charHandle = {};
+ ObjCStrongReference<NSData> value;
+};
+
+@interface QT_MANGLE_NAMESPACE(OSXBTPeripheralManager) : NSObject<CBPeripheralManagerDelegate>
+{
+ ObjCScopedPointer<CBPeripheralManager> manager;
+ LECBManagerNotifier *notifier;
+
+ QLowEnergyHandle lastHandle;
+ // Services in this vector are placed in such order:
+ // the one that has included services, must
+ // follow its included services to avoid exceptions from CBPeripheralManager.
+ std::vector<ObjCStrongReference<CBMutableService>> services;
+ decltype(services.size()) nextServiceToAdd;
+
+ // Lookup map for included services:
+ std::map<QBluetoothUuid, CBService *> serviceIndex;
+ ObjCScopedPointer<NSMutableDictionary> advertisementData;
+
+ GenericLEMap<CBCharacteristic *> charMap;
+ GenericLEMap<ObjCStrongReference<NSData>> charValues;
+
+ std::deque<UpdateRequest> updateQueue;
+
+ ObjCScopedPointer<NSMutableSet> connectedCentrals;
+
+ PeripheralState state;
+}
+
+- (id)initWith:(LECBManagerNotifier *)notifier;
+- (void)dealloc;
+
+- (QSharedPointer<QLowEnergyServicePrivate>)addService:(const QLowEnergyServiceData &)data;
+- (void) setParameters:(const QLowEnergyAdvertisingParameters &)parameters
+ data:(const QLowEnergyAdvertisingData &)data
+ scanResponse:(const QLowEnergyAdvertisingData &)scanResponse;
+
+// To be executed on the Qt's special BTLE dispatch queue.
+- (void)startAdvertising;
+- (void)stopAdvertising;
+- (void)detach;
+
+- (void)write:(const QByteArray &)value
+ charHandle:(QLowEnergyHandle)charHandle;
+
+
+// CBPeripheralManagerDelegate's callbacks (BTLE queue).
+- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ willRestoreState:(NSDictionary *)dict;
+- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
+ error:(NSError *)error;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didAddService:(CBService *)service error:(NSError *)error;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
+ didSubscribeToCharacteristic:(CBCharacteristic *)characteristic;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
+ didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didReceiveReadRequest:(CBATTRequest *)request;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didReceiveWriteRequests:(NSArray *)requests;
+- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral;
+
+@end
+
+#endif
diff --git a/src/bluetooth/osx/osxbtutility.mm b/src/bluetooth/osx/osxbtutility.mm
index ef34b63f..f579b2a4 100644
--- a/src/bluetooth/osx/osxbtutility.mm
+++ b/src/bluetooth/osx/osxbtutility.mm
@@ -37,6 +37,7 @@
**
****************************************************************************/
+#include "qlowenergycharacteristicdata.h"
#include "qbluetoothaddress.h"
#include "osxbtutility_p.h"
#include "qbluetoothuuid.h"
diff --git a/src/bluetooth/osx/osxbtutility_p.h b/src/bluetooth/osx/osxbtutility_p.h
index 14178162..5e616b74 100644
--- a/src/bluetooth/osx/osxbtutility_p.h
+++ b/src/bluetooth/osx/osxbtutility_p.h
@@ -64,6 +64,8 @@
QT_BEGIN_NAMESPACE
+class QLowEnergyCharacteristicData;
+class QBluetoothAddress;
class QBluetoothUuid;
namespace OSXBluetooth {
@@ -282,7 +284,7 @@ QString qt_address(NSString *address);
#ifndef QT_IOS_BLUETOOTH
-class QBluetoothAddress qt_address(const BluetoothDeviceAddress *address);
+QBluetoothAddress qt_address(const BluetoothDeviceAddress *address);
BluetoothDeviceAddress iobluetooth_address(const QBluetoothAddress &address);
ObjCStrongReference<IOBluetoothSDPUUID> iobluetooth_uuid(const QBluetoothUuid &uuid);
diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm
index 47f65965..1e0f84d8 100644
--- a/src/bluetooth/qlowenergycontroller_osx.mm
+++ b/src/bluetooth/qlowenergycontroller_osx.mm
@@ -38,11 +38,14 @@
**
****************************************************************************/
+#include "osx/osxbtnotifier_p.h"
#include "osx/osxbtutility_p.h"
#include "osx/uistrings_p.h"
+
#include "qlowenergyserviceprivate_p.h"
#include "qlowenergycontroller_osx_p.h"
+#include "qlowenergyservicedata.h"
#include "qbluetoothlocaldevice.h"
#include "qbluetoothdeviceinfo.h"
#include "qlowenergycontroller.h"
@@ -136,86 +139,84 @@ UUIDList qt_servicesUuids(NSArray *services)
}
-QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController *q)
+QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController::Role r,
+ QLowEnergyController *q,
+ const QBluetoothDeviceInfo &deviceInfo)
: q_ptr(q),
+ deviceUuid(deviceInfo.deviceUuid()),
+ deviceName(deviceInfo.name()),
lastError(QLowEnergyController::NoError),
controllerState(QLowEnergyController::UnconnectedState),
addressType(QLowEnergyController::PublicAddress)
{
registerQLowEnergyControllerMetaType();
- // This is the "wrong" constructor - no valid device UUID to connect later.
Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)");
- using OSXBluetooth::LECentralNotifier;
+ using OSXBluetooth::LECBManagerNotifier;
+ using OSXBluetooth::qt_OS_limit;
- // We still create a manager, to simplify error handling later.
- QScopedPointer<LECentralNotifier> notifier(new LECentralNotifier);
- centralManager.reset([[ObjCCentralManager alloc] initWith:notifier.data()]);
- if (!centralManager) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "failed to initialize central manager";
- return;
- } else if (!connectSlots(notifier.data())) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "failed to connect to notifier's signals";
- }
+ role = r;
- // Ownership was taken by central manager.
- notifier.take();
-}
-QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController *q,
- const QBluetoothDeviceInfo &deviceInfo)
- : q_ptr(q),
- deviceUuid(deviceInfo.deviceUuid()),
- deviceName(deviceInfo.name()),
- lastError(QLowEnergyController::NoError),
- controllerState(QLowEnergyController::UnconnectedState),
- addressType(QLowEnergyController::PublicAddress)
-{
- registerQLowEnergyControllerMetaType();
- Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)");
-
- using OSXBluetooth::LECentralNotifier;
+ QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier);
+ if (role == QLowEnergyController::PeripheralRole) {
+ if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_6_0)) {
+ peripheralManager.reset([[ObjCPeripheralManager alloc] initWith:notifier.data()]);
+ if (!peripheralManager) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO
+ << "failed to initialize peripheral manager";
+ return;
+ }
+ } else {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO
+ << "peripheral role is not supported on your platform";
+ return;
+ }
+ } else {
+ centralManager.reset([[ObjCCentralManager alloc] initWith:notifier.data()]);
+ if (!centralManager) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO
+ << "failed to initialize central manager";
+ return;
+ }
+ }
- QScopedPointer<LECentralNotifier> notifier(new LECentralNotifier);
- centralManager.reset([[ObjCCentralManager alloc] initWith:notifier.data()]);
- if (!centralManager) {
+ if (!connectSlots(notifier.data())) {
qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "failed to initialize central manager";
- return;
- } else if (!connectSlots(notifier.data())) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "failed to connect to notifier's signals";
+ << "failed to connect to notifier's signal(s)";
}
-
// Ownership was taken by central manager.
notifier.take();
}
QLowEnergyControllerPrivateOSX::~QLowEnergyControllerPrivateOSX()
{
- // TODO: dispatch_sync 'setDelegate:Q_NULLPRT' to our CBCentralManager's delegate.
- if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) {
- ObjCCentralManager *manager = centralManager.data();
- dispatch_sync(leQueue, ^{
- [manager detach];
- });
+ if (const auto leQueue = OSXBluetooth::qt_LE_queue()) {
+ if (role == QLowEnergyController::CentralRole) {
+ const auto manager = centralManager.data();
+ dispatch_sync(leQueue, ^{
+ [manager detach];
+ });
+ } else {
+ if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_6_0)) {
+ const auto manager = peripheralManager.data();
+ dispatch_sync(leQueue, ^{
+ [manager detach];
+ });
+ }
+ }
}
}
bool QLowEnergyControllerPrivateOSX::isValid() const
{
- return centralManager;
+ return centralManager || peripheralManager;
}
void QLowEnergyControllerPrivateOSX::_q_connected()
{
- Q_ASSERT_X(controllerState == QLowEnergyController::ConnectingState,
- Q_FUNC_INFO, "invalid state");
-
controllerState = QLowEnergyController::ConnectedState;
emit q_ptr->stateChanged(QLowEnergyController::ConnectedState);
@@ -226,10 +227,11 @@ void QLowEnergyControllerPrivateOSX::_q_disconnected()
{
controllerState = QLowEnergyController::UnconnectedState;
- invalidateServices();
+ if (role == QLowEnergyController::CentralRole)
+ invalidateServices();
+
emit q_ptr->stateChanged(QLowEnergyController::UnconnectedState);
emit q_ptr->disconnected();
-
}
void QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished()
@@ -461,7 +463,7 @@ void QLowEnergyControllerPrivateOSX::_q_LEnotSupported()
// be supported.
}
-void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(QLowEnergyController::Error errorCode)
+void QLowEnergyControllerPrivateOSX::_q_CBManagerError(QLowEnergyController::Error errorCode)
{
// Errors reported during connect and general errors.
@@ -478,8 +480,8 @@ void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(QLowEnergyControll
// a service/characteristic - related error.
}
-void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(const QBluetoothUuid &serviceUuid,
- QLowEnergyController::Error errorCode)
+void QLowEnergyControllerPrivateOSX::_q_CBManagerError(const QBluetoothUuid &serviceUuid,
+ QLowEnergyController::Error errorCode)
{
// Errors reported while discovering service details etc.
Q_UNUSED(errorCode) // TODO: setError?
@@ -494,8 +496,8 @@ void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(const QBluetoothUu
}
}
-void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(const QBluetoothUuid &serviceUuid,
- QLowEnergyService::ServiceError errorCode)
+void QLowEnergyControllerPrivateOSX::_q_CBManagerError(const QBluetoothUuid &serviceUuid,
+ QLowEnergyService::ServiceError errorCode)
{
if (!discoveredServices.contains(serviceUuid)) {
qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "unknown service uuid: "
@@ -514,6 +516,8 @@ void QLowEnergyControllerPrivateOSX::connectToDevice()
Q_FUNC_INFO, "invalid state");
Q_ASSERT_X(!deviceUuid.isNull(), Q_FUNC_INFO,
"invalid private controller (no device uuid)");
+ Q_ASSERT_X(role != QLowEnergyController::PeripheralRole,
+ Q_FUNC_INFO, "invalid role (peripheral)");
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
@@ -537,6 +541,8 @@ void QLowEnergyControllerPrivateOSX::discoverServices()
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid private controller");
Q_ASSERT_X(controllerState != QLowEnergyController::UnconnectedState,
Q_FUNC_INFO, "not connected to peripheral");
+ Q_ASSERT_X(role != QLowEnergyController::PeripheralRole,
+ Q_FUNC_INFO, "invalid role (peripheral)");
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
@@ -559,6 +565,8 @@ void QLowEnergyControllerPrivateOSX::discoverServiceDetails(const QBluetoothUuid
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid private controller");
if (controllerState != QLowEnergyController::DiscoveredState) {
+ // This will also exclude peripheral role, since controller
+ // can never be in discovered state ...
qCWarning(QT_BT_OSX) << Q_FUNC_INFO
<< "can not discover service details in the current state, "
<< "QLowEnergyController::DiscoveredState is expected";
@@ -593,6 +601,12 @@ void QLowEnergyControllerPrivateOSX::setNotifyValue(QSharedPointer<QLowEnergySer
Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
+ if (role == QLowEnergyController::PeripheralRole) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid role (peripheral)";
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return;
+ }
+
if (newValue.size() > 2) {
// Qt's API requires an error on such write.
// With Core Bluetooth we do not write any descriptor,
@@ -637,6 +651,11 @@ void QLowEnergyControllerPrivateOSX::readCharacteristic(QSharedPointer<QLowEnerg
Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
+ if (role == QLowEnergyController::PeripheralRole) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid role (peripheral)";
+ return;
+ }
+
if (!discoveredServices.contains(service->uuid)) {
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid:"
<< service->uuid << "found";
@@ -669,9 +688,9 @@ void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEner
Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
- // We can work only with services, found on a given peripheral
- // (== created by the given LE controller),
- // otherwise we can not write anything at all.
+ // We can work only with services found on a given peripheral
+ // (== created by the given LE controller).
+
if (!discoveredServices.contains(service->uuid)) {
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid: "
<< service->uuid << " found";
@@ -689,16 +708,29 @@ void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEner
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
return;
}
- // Attention! Copy objects!
- const QBluetoothUuid serviceUuid(service->uuid);
+ // Attention! We have to copy objects!
const QByteArray newValueCopy(newValue);
- ObjCCentralManager *const manager = centralManager.data();
- dispatch_async(leQueue, ^{
- [manager write:newValueCopy
- charHandle:charHandle
+ if (role == QLowEnergyController::CentralRole) {
+ const QBluetoothUuid serviceUuid(service->uuid);
+ const auto manager = centralManager.data();
+ dispatch_async(leQueue, ^{
+ [manager write:newValueCopy
+ charHandle:charHandle
onService:serviceUuid
withResponse:mode == QLowEnergyService::WriteWithResponse];
- });
+ });
+ } else {
+
+ if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_6_0)) {
+ const auto manager = peripheralManager.data();
+ dispatch_async(leQueue, ^{
+ [manager write:newValueCopy charHandle:charHandle];
+ });
+ } else {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO
+ << "peripheral role is not supported on your platform";
+ }
+ }
}
quint16 QLowEnergyControllerPrivateOSX::updateValueOfCharacteristic(QLowEnergyHandle charHandle,
@@ -728,6 +760,11 @@ void QLowEnergyControllerPrivateOSX::readDescriptor(QSharedPointer<QLowEnergySer
Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
+ if (role == QLowEnergyController::PeripheralRole) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid role (peripheral)";
+ return;
+ }
+
if (!discoveredServices.contains(service->uuid)) {
qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid:"
<< service->uuid << "found";
@@ -755,6 +792,11 @@ void QLowEnergyControllerPrivateOSX::writeDescriptor(QSharedPointer<QLowEnergySe
Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)");
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller");
+ if (role == QLowEnergyController::PeripheralRole) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid role (peripheral)";
+ return;
+ }
+
// We can work only with services found on a given peripheral
// (== created by the given LE controller),
// otherwise we can not write anything at all.
@@ -868,17 +910,23 @@ void QLowEnergyControllerPrivateOSX::setErrorDescription(QLowEnergyController::E
errorString.clear();
break;
case QLowEnergyController::UnknownRemoteDeviceError:
- errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_RDEV_NO_FOUND);
+ errorString = QLowEnergyController::tr("Remote device cannot be found");
break;
case QLowEnergyController::InvalidBluetoothAdapterError:
- errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_NO_LOCAL_DEV);
+ errorString = QLowEnergyController::tr("Cannot find local adapter");
break;
case QLowEnergyController::NetworkError:
- errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_IO_ERROR);
+ errorString = QLowEnergyController::tr("Error occurred during connection I/O");
+ break;
+ case QLowEnergyController::ConnectionError:
+ errorString = QLowEnergyController::tr("Error occurred trying to connect to remote device.");
+ break;
+ case QLowEnergyController::AdvertisingError:
+ errorString = QLowEnergyController::tr("Error occurred trying to start advertising");
break;
case QLowEnergyController::UnknownError:
default:
- errorString = QCoreApplication::translate(LE_CONTROLLER, LEC_UNKNOWN_ERROR);
+ errorString = QLowEnergyController::tr("Unknown Error");
break;
}
}
@@ -893,38 +941,38 @@ void QLowEnergyControllerPrivateOSX::invalidateServices()
discoveredServices.clear();
}
-bool QLowEnergyControllerPrivateOSX::connectSlots(OSXBluetooth::LECentralNotifier *notifier)
+bool QLowEnergyControllerPrivateOSX::connectSlots(OSXBluetooth::LECBManagerNotifier *notifier)
{
- using OSXBluetooth::LECentralNotifier;
+ using OSXBluetooth::LECBManagerNotifier;
Q_ASSERT_X(notifier, Q_FUNC_INFO, "invalid notifier object (null)");
- bool ok = connect(notifier, &LECentralNotifier::connected,
+ bool ok = connect(notifier, &LECBManagerNotifier::connected,
this, &QLowEnergyControllerPrivateOSX::_q_connected);
- ok = ok && connect(notifier, &LECentralNotifier::disconnected,
+ ok = ok && connect(notifier, &LECBManagerNotifier::disconnected,
this, &QLowEnergyControllerPrivateOSX::_q_disconnected);
- ok = ok && connect(notifier, &LECentralNotifier::serviceDiscoveryFinished,
+ ok = ok && connect(notifier, &LECBManagerNotifier::serviceDiscoveryFinished,
this, &QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished);
- ok = ok && connect(notifier, &LECentralNotifier::serviceDetailsDiscoveryFinished,
+ ok = ok && connect(notifier, &LECBManagerNotifier::serviceDetailsDiscoveryFinished,
this, &QLowEnergyControllerPrivateOSX::_q_serviceDetailsDiscoveryFinished);
- ok = ok && connect(notifier, &LECentralNotifier::characteristicRead,
+ ok = ok && connect(notifier, &LECBManagerNotifier::characteristicRead,
this, &QLowEnergyControllerPrivateOSX::_q_characteristicRead);
- ok = ok && connect(notifier, &LECentralNotifier::characteristicWritten,
+ ok = ok && connect(notifier, &LECBManagerNotifier::characteristicWritten,
this, &QLowEnergyControllerPrivateOSX::_q_characteristicWritten);
- ok = ok && connect(notifier, &LECentralNotifier::characteristicUpdated,
+ ok = ok && connect(notifier, &LECBManagerNotifier::characteristicUpdated,
this, &QLowEnergyControllerPrivateOSX::_q_characteristicUpdated);
- ok = ok && connect(notifier, &LECentralNotifier::descriptorRead,
+ ok = ok && connect(notifier, &LECBManagerNotifier::descriptorRead,
this, &QLowEnergyControllerPrivateOSX::_q_descriptorRead);
- ok = ok && connect(notifier, &LECentralNotifier::descriptorWritten,
+ ok = ok && connect(notifier, &LECBManagerNotifier::descriptorWritten,
this, &QLowEnergyControllerPrivateOSX::_q_descriptorWritten);
- ok = ok && connect(notifier, &LECentralNotifier::LEnotSupported,
+ ok = ok && connect(notifier, &LECBManagerNotifier::LEnotSupported,
this, &QLowEnergyControllerPrivateOSX::_q_LEnotSupported);
- ok = ok && connect(notifier, SIGNAL(CBCentralManagerError(QLowEnergyController::Error)),
- this, SLOT(_q_CBCentralManagerError(QLowEnergyController::Error)));
- ok = ok && connect(notifier, SIGNAL(CBCentralManagerError(const QBluetoothUuid &, QLowEnergyController::Error)),
- this, SLOT(_q_CBCentralManagerError(const QBluetoothUuid &, QLowEnergyController::Error)));
- ok = ok && connect(notifier, SIGNAL(CBCentralManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError)),
- this, SLOT(_q_CBCentralManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError)));
+ ok = ok && connect(notifier, SIGNAL(CBManagerError(QLowEnergyController::Error)),
+ this, SLOT(_q_CBManagerError(QLowEnergyController::Error)));
+ ok = ok && connect(notifier, SIGNAL(CBManagerError(const QBluetoothUuid &, QLowEnergyController::Error)),
+ this, SLOT(_q_CBManagerError(const QBluetoothUuid &, QLowEnergyController::Error)));
+ ok = ok && connect(notifier, SIGNAL(CBManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError)),
+ this, SLOT(_q_CBManagerError(const QBluetoothUuid &, QLowEnergyService::ServiceError)));
if (!ok)
notifier->disconnect();
@@ -935,11 +983,10 @@ bool QLowEnergyControllerPrivateOSX::connectSlots(OSXBluetooth::LECentralNotifie
QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddress,
QObject *parent)
: QObject(parent),
- d_ptr(new QLowEnergyControllerPrivateOSX(this))
+ d_ptr(new QLowEnergyControllerPrivateOSX(CentralRole, this))
{
OSX_D_PTR;
- osx_d_ptr->role = CentralRole;
osx_d_ptr->remoteAddress = remoteAddress;
osx_d_ptr->localAddress = QBluetoothLocalDevice().address();
@@ -950,11 +997,10 @@ QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddres
QLowEnergyController::QLowEnergyController(const QBluetoothDeviceInfo &remoteDevice,
QObject *parent)
: QObject(parent),
- d_ptr(new QLowEnergyControllerPrivateOSX(this, remoteDevice))
+ d_ptr(new QLowEnergyControllerPrivateOSX(CentralRole, this, remoteDevice))
{
OSX_D_PTR;
- osx_d_ptr->role = CentralRole;
osx_d_ptr->localAddress = QBluetoothLocalDevice().address();
// That's the only "real" ctor - with Core Bluetooth we need a _valid_ deviceUuid
// from 'remoteDevice'.
@@ -964,11 +1010,10 @@ QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddres
const QBluetoothAddress &localAddress,
QObject *parent)
: QObject(parent),
- d_ptr(new QLowEnergyControllerPrivateOSX(this))
+ d_ptr(new QLowEnergyControllerPrivateOSX(CentralRole, this))
{
OSX_D_PTR;
- osx_d_ptr->role = CentralRole;
osx_d_ptr->remoteAddress = remoteAddress;
osx_d_ptr->localAddress = localAddress;
@@ -977,11 +1022,11 @@ QLowEnergyController::QLowEnergyController(const QBluetoothAddress &remoteAddres
}
QLowEnergyController::QLowEnergyController(QObject *parent)
- : QObject(parent), d_ptr(new QLowEnergyControllerPrivateOSX(this))
+ : QObject(parent),
+ d_ptr(new QLowEnergyControllerPrivateOSX(PeripheralRole, this))
{
OSX_D_PTR;
- osx_d_ptr->role = PeripheralRole;
osx_d_ptr->localAddress = QBluetoothLocalDevice().address();
}
@@ -1059,11 +1104,16 @@ void QLowEnergyController::connectToDevice()
// A memory allocation problem.
if (!osx_d_ptr->isValid())
- return osx_d_ptr->_q_CBCentralManagerError(UnknownError);
+ return osx_d_ptr->_q_CBManagerError(UnknownError);
+
+ if (role() == PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "can not connect in peripheral role";
+ return osx_d_ptr->_q_CBManagerError(ConnectionError);
+ }
// No QBluetoothDeviceInfo provided during construction.
if (osx_d_ptr->deviceUuid.isNull())
- return osx_d_ptr->_q_CBCentralManagerError(UnknownRemoteDeviceError);
+ return osx_d_ptr->_q_CBManagerError(UnknownRemoteDeviceError);
if (osx_d_ptr->controllerState != UnconnectedState)
return;
@@ -1078,6 +1128,11 @@ void QLowEnergyController::disconnectFromDevice()
OSX_D_PTR;
+ if (role() != CentralRole) {
+ qCWarning(QT_BT_OSX) << "can not disconnect while in central role";
+ return osx_d_ptr->_q_CBManagerError(ConnectionError);
+ }
+
if (osx_d_ptr->isValid()) {
const ControllerState oldState = osx_d_ptr->controllerState;
@@ -1109,6 +1164,12 @@ void QLowEnergyController::disconnectFromDevice()
void QLowEnergyController::discoverServices()
{
+ if (role() == PeripheralRole) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO
+ << "invalid role (peripheral)";
+ return;
+ }
+
if (state() != ConnectedState)
return;
@@ -1159,23 +1220,102 @@ void QLowEnergyController::startAdvertising(const QLowEnergyAdvertisingParameter
const QLowEnergyAdvertisingData &advertisingData,
const QLowEnergyAdvertisingData &scanResponseData)
{
- Q_UNUSED(params);
- Q_UNUSED(advertisingData);
- Q_UNUSED(scanResponseData);
- qCWarning(QT_BT_OSX) << "LE advertising not implemented for OS X";
+ OSX_D_PTR;
+
+ if (!osx_d_ptr->isValid())
+ return osx_d_ptr->_q_CBManagerError(UnknownError);
+
+ if (role() != PeripheralRole) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid role";
+ return;
+ }
+
+ if (state() != UnconnectedState) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid state" << state();
+ return;
+ }
+
+ auto leQueue(OSXBluetooth::qt_LE_queue());
+ if (!leQueue) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ osx_d_ptr->setErrorDescription(QLowEnergyController::UnknownError);
+ return;
+ }
+
+ [osx_d_ptr->peripheralManager setParameters:params
+ data:advertisingData
+ scanResponse:scanResponseData];
+
+ osx_d_ptr->controllerState = AdvertisingState;
+ emit stateChanged(AdvertisingState);
+
+ const auto manager = osx_d_ptr->peripheralManager.data();
+ dispatch_async(leQueue, ^{
+ [manager startAdvertising];
+ });
}
void QLowEnergyController::stopAdvertising()
{
- qCWarning(QT_BT_OSX) << "LE advertising not implemented for OS X";
+ OSX_D_PTR;
+
+ if (!osx_d_ptr->isValid())
+ return osx_d_ptr->_q_CBManagerError(UnknownError);
+
+ if (state() != AdvertisingState) {
+ qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "called in state" << state();
+ return;
+ }
+
+ if (const auto leQueue = OSXBluetooth::qt_LE_queue()) {
+ const auto manager = osx_d_ptr->peripheralManager.data();
+ dispatch_sync(leQueue, ^{
+ [manager stopAdvertising];
+ });
+
+ osx_d_ptr->controllerState = UnconnectedState;
+ emit stateChanged(UnconnectedState);
+ } else {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ osx_d_ptr->setErrorDescription(QLowEnergyController::UnknownError);
+ return;
+ }
}
-QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData &service,
+QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData &data,
QObject *parent)
{
- Q_UNUSED(service);
- Q_UNUSED(parent);
- qCWarning(QT_BT_OSX) << "GATT server functionality not implemented for OS X";
+ OSX_D_PTR;
+
+ if (!osx_d_ptr->isValid()) {
+ osx_d_ptr->_q_CBManagerError(UnknownError);
+ return nullptr;
+ }
+
+ if (role() != PeripheralRole) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "not in peripheral role";
+ return nullptr;
+ }
+
+ if (state() != UnconnectedState) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid state";
+ return nullptr;
+ }
+
+ if (!data.isValid()) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid service";
+ return nullptr;
+ }
+
+ for (auto includedService : data.includedServices())
+ includedService->d_ptr->type |= QLowEnergyService::IncludedService;
+
+ if (const auto servicePrivate = [osx_d_ptr->peripheralManager addService:data]) {
+ servicePrivate->setController(osx_d_ptr);
+ osx_d_ptr->discoveredServices.insert(servicePrivate->uuid, servicePrivate);
+ return new QLowEnergyService(servicePrivate, parent);
+ }
+
return nullptr;
}
diff --git a/src/bluetooth/qlowenergycontroller_osx_p.h b/src/bluetooth/qlowenergycontroller_osx_p.h
index 853e1f9b..f6e2a03e 100644
--- a/src/bluetooth/qlowenergycontroller_osx_p.h
+++ b/src/bluetooth/qlowenergycontroller_osx_p.h
@@ -51,6 +51,7 @@
// We mean it.
//
+#include "osx/osxbtperipheralmanager_p.h"
#include "qlowenergyserviceprivate_p.h"
#include "osx/osxbtcentralmanager_p.h"
#include "qlowenergycontroller_p.h"
@@ -61,6 +62,7 @@
#include "qbluetoothuuid.h"
#include <QtCore/qsharedpointer.h>
+#include <QtCore/qsysinfo.h>
#include <QtCore/qglobal.h>
#include <QtCore/qstring.h>
#include <QtCore/qmap.h>
@@ -70,13 +72,13 @@ QT_BEGIN_NAMESPACE
namespace OSXBluetooth
{
-class LECentralNotifier;
+class LECBManagerNotifier;
}
class QByteArray;
-// While suffix 'OSX', it's also for iOS.
+// Suffix 'OSX' is a legacy, it's also iOS.
class QLowEnergyControllerPrivateOSX : public QLowEnergyControllerPrivate
{
friend class QLowEnergyController;
@@ -84,9 +86,8 @@ class QLowEnergyControllerPrivateOSX : public QLowEnergyControllerPrivate
Q_OBJECT
public:
- QLowEnergyControllerPrivateOSX(QLowEnergyController *q);
- QLowEnergyControllerPrivateOSX(QLowEnergyController *q,
- const QBluetoothDeviceInfo &uuid);
+ QLowEnergyControllerPrivateOSX(QLowEnergyController::Role role, QLowEnergyController *q,
+ const QBluetoothDeviceInfo &info = QBluetoothDeviceInfo());
~QLowEnergyControllerPrivateOSX();
bool isValid() const;
@@ -105,18 +106,15 @@ private Q_SLOTS:
void _q_descriptorWritten(QLowEnergyHandle charHandle, const QByteArray &value);
void _q_LEnotSupported();
- void _q_CBCentralManagerError(QLowEnergyController::Error error);
- void _q_CBCentralManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyController::Error error);
- void _q_CBCentralManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error);
+ void _q_CBManagerError(QLowEnergyController::Error error);
+ void _q_CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyController::Error error);
+ void _q_CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error);
private:
void connectToDevice();
void discoverServices();
void discoverServiceDetails(const QBluetoothUuid &serviceUuid);
- // TODO: all these read/write /setNotify can be simplified -
- // by just passing either characteristic or descriptor (that
- // has all needed information - service, handle, etc.).
void setNotifyValue(QSharedPointer<QLowEnergyServicePrivate> service,
QLowEnergyHandle charHandle, const QByteArray &newValue);
@@ -149,7 +147,7 @@ private:
void setErrorDescription(QLowEnergyController::Error errorCode);
void invalidateServices();
- bool connectSlots(OSXBluetooth::LECentralNotifier *notifier);
+ bool connectSlots(OSXBluetooth::LECBManagerNotifier *notifier);
QLowEnergyController *q_ptr;
QBluetoothUuid deviceUuid;
@@ -170,6 +168,10 @@ private:
typedef OSXBluetooth::ObjCScopedPointer<ObjCCentralManager> CentralManager;
CentralManager centralManager;
+ typedef QT_MANGLE_NAMESPACE(OSXBTPeripheralManager) ObjCPeripheralManager;
+ typedef OSXBluetooth::ObjCScopedPointer<ObjCPeripheralManager> PeripheralManager;
+ PeripheralManager peripheralManager;
+
typedef QMap<QBluetoothUuid, QSharedPointer<QLowEnergyServicePrivate> > ServiceMap;
typedef ServiceMap::const_iterator ConstServiceIterator;
typedef ServiceMap::iterator ServiceIterator;
diff --git a/src/bluetooth/qlowenergyservice_osx.mm b/src/bluetooth/qlowenergyservice_osx.mm
index 97d64040..52c2ac87 100644
--- a/src/bluetooth/qlowenergyservice_osx.mm
+++ b/src/bluetooth/qlowenergyservice_osx.mm
@@ -208,7 +208,9 @@ void QLowEnergyService::writeCharacteristic(const QLowEnergyCharacteristic &ch,
WriteMode mode)
{
QLowEnergyControllerPrivateOSX *const controller = qt_mac_le_controller(d_ptr);
- if (controller == Q_NULLPTR || state() != ServiceDiscovered || !contains(ch)) {
+ if (controller == Q_NULLPTR ||
+ (controller->role == QLowEnergyController::CentralRole && state() != ServiceDiscovered) ||
+ !contains(ch)) {
d_ptr->setError(QLowEnergyService::OperationError);
return;
}
@@ -250,6 +252,8 @@ void QLowEnergyService::writeDescriptor(const QLowEnergyDescriptor &descriptor,
{
QLowEnergyControllerPrivateOSX *const controller = qt_mac_le_controller(d_ptr);
if (controller == Q_NULLPTR || state() != ServiceDiscovered || !contains(descriptor)) {
+ // This operation error also includes LE controller in the peripheral role:
+ // on iOS/OS X - descriptors are immutable.
d_ptr->setError(OperationError);
return;
}