diff options
author | Timur Pocheptsov <timur.pocheptsov@theqtcompany.com> | 2016-02-07 13:02:34 +0100 |
---|---|---|
committer | Timur Pocheptsov <timur.pocheptsov@theqtcompany.com> | 2016-03-13 11:56:46 +0000 |
commit | f51643a314af54400301bcb687829e258a857ac3 (patch) | |
tree | 911926af78e9e0db0e28a6c9a66aed8db8d1cf71 /src | |
parent | 51e32a9f7972e31e65bccf36caa238ac245091e0 (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>
Diffstat (limited to 'src')
-rw-r--r-- | src/bluetooth/osx/osxbt.pri | 13 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtcentralmanager.mm | 54 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtcentralmanager_p.h | 6 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtnotifier_p.h | 8 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtperipheralmanager.mm | 754 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtperipheralmanager_p.h | 176 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtutility.mm | 1 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtutility_p.h | 4 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_osx.mm | 354 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_osx_p.h | 26 | ||||
-rw-r--r-- | src/bluetooth/qlowenergyservice_osx.mm | 6 |
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; } |