Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
btledeviceinquiry.mm
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6#include "qbluetoothuuid.h"
7#include "btnotifier_p.h"
8#include "btutility_p.h"
9
10#include <QtCore/qloggingcategory.h>
11#include <QtCore/qdebug.h>
12#include <QtCore/qendian.h>
13#include <QtCore/qlist.h>
14
15#include <algorithm>
16
18
19namespace DarwinBluetooth {
20
21QBluetoothUuid qt_uuid(NSUUID *nsUuid)
22{
23 if (!nsUuid)
24 return QBluetoothUuid();
25
26 uuid_t uuidData = {};
27 [nsUuid getUUIDBytes:uuidData];
28 QUuid::Id128Bytes qtUuidData = {};
29 std::copy(uuidData, uuidData + 16, qtUuidData.data);
30 return QBluetoothUuid(qtUuidData);
31}
32
33const int timeStepMS = 100;
34const int powerOffTimeoutMS = 30000;
35
37 // That's what CoreBluetooth has:
38 // CBAdvertisementDataLocalNameKey
39 // CBAdvertisementDataTxPowerLevelKey
40 // CBAdvertisementDataServiceUUIDsKey
41 // CBAdvertisementDataServiceDataKey
42 // CBAdvertisementDataManufacturerDataKey
43 // CBAdvertisementDataOverflowServiceUUIDsKey
44 // CBAdvertisementDataIsConnectable
45 // CBAdvertisementDataSolicitedServiceUUIDsKey
46
47 // For now, we "parse":
49 QList<QBluetoothUuid> serviceUuids;
50 QHash<quint16, QByteArray> manufacturerData;
51 QHash<QBluetoothUuid, QByteArray> serviceData;
52 // TODO: other keys probably?
54};
55
57{
59 return;
60
61 // ... constant CBAdvertisementDataLocalNameKey ...
62 // NSString containing the local name of a peripheral.
63 NSObject *value = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
64 if (value && [value isKindOfClass:[NSString class]])
65 localName = QString::fromNSString(static_cast<NSString *>(value));
66
67 // ... constant CBAdvertisementDataServiceUUIDsKey ...
68 // A list of one or more CBUUID objects, representing CBService UUIDs.
69
70 value = [advertisementData objectForKey:CBAdvertisementDataServiceUUIDsKey];
71 if (value && [value isKindOfClass:[NSArray class]]) {
72 NSArray *uuids = static_cast<NSArray *>(value);
73 for (CBUUID *cbUuid in uuids)
74 serviceUuids << qt_uuid(cbUuid);
75 }
76
77 NSDictionary *advdict = [advertisementData objectForKey:CBAdvertisementDataServiceDataKey];
78 if (advdict) {
79 [advdict enumerateKeysAndObjectsUsingBlock:^(CBUUID *key, NSData *val, BOOL *) {
80 serviceData.insert(qt_uuid(key), QByteArray::fromNSData(static_cast<NSData *>(val)));
81 }];
82 }
83
84 value = [advertisementData objectForKey:CBAdvertisementDataManufacturerDataKey];
85 if (value && [value isKindOfClass:[NSData class]]) {
86 QByteArray data = QByteArray::fromNSData(static_cast<NSData *>(value));
87 manufacturerData.insert(qFromLittleEndian<quint16>(data.constData()), data.mid(2));
88 }
89}
90
91}
92
94
96
97@interface DarwinBTLEDeviceInquiry (PrivateAPI)
100@end
101
103{
104 LECBManagerNotifier *notifier;
105 ObjCScopedPointer<CBCentralManager> manager;
106
107 QList<QBluetoothDeviceInfo> devices;
110
111 QT_PREPEND_NAMESPACE(DarwinBluetooth)::GCDTimer elapsedTimer;
112}
113
114-(id)initWithNotifier:(LECBManagerNotifier *)aNotifier
115{
116 if (self = [super init]) {
117 Q_ASSERT(aNotifier);
118 notifier = aNotifier;
121 }
122
123 return self;
124}
125
126- (void)dealloc
127{
128 [self stopScanSafe];
129 [manager setDelegate:nil];
130 [elapsedTimer cancelTimer];
131 [self stopNotifier];
132 [super dealloc];
133}
134
135- (void)timeout:(id)sender
136{
137 Q_UNUSED(sender);
138
140 [self stopScanSafe];
141 [manager setDelegate:nil];
145 } else if (internalState == InquiryStarting) {
146 // This is interesting on iOS only, where the system shows an alert
147 // asking to enable Bluetooth in the 'Settings' app. If not done yet
148 // (after 30 seconds) - we consider this as an error.
149 [manager setDelegate:nil];
153 }
154}
155
156- (void)startWithTimeout:(int)timeout
157{
158 dispatch_queue_t leQueue(DarwinBluetooth::qt_LE_queue());
159 Q_ASSERT(leQueue);
161 manager.reset([[CBCentralManager alloc] initWithDelegate:self queue:leQueue],
162 DarwinBluetooth::RetainPolicy::noInitialRetain);
163}
164
165- (void)centralManagerDidUpdateState:(CBCentralManager *)central
166{
167#pragma clang diagnostic push
168#pragma clang diagnostic ignored "-Wunguarded-availability-new"
169
170 if (central != manager)
171 return;
172
174 return;
175
177
178 using namespace DarwinBluetooth;
179
180 const auto state = central.state;
181 if (state == CBManagerStatePoweredOn) {
184
185 if (inquiryTimeoutMS > 0) {
186 [elapsedTimer cancelTimer];
187 elapsedTimer.reset([[DarwinBTGCDTimer alloc] initWithDelegate:self], RetainPolicy::noInitialRetain);
188 [elapsedTimer startWithTimeout:inquiryTimeoutMS step:timeStepMS];
189 }
190
191 // ### Qt 6.x: remove the use of env. variable, as soon as a proper public API is in place.
192 bool envOk = false;
193 const int env = qEnvironmentVariableIntValue("QT_BLUETOOTH_SCAN_ENABLE_DUPLICATES", &envOk);
194 if (envOk && env) {
195 [manager scanForPeripheralsWithServices:nil
196 options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES}];
197 } else {
198 [manager scanForPeripheralsWithServices:nil options:nil];
199 }
200 } // Else we ignore.
201 } else if (state == CBManagerStateUnsupported) {
203 [self stopScanSafe];
204 // Not sure how this is possible at all,
205 // probably, can never happen.
208 } else {
211 }
212 [manager setDelegate:nil];
213 } else if (state == CBManagerStateUnauthorized) {
215 [self stopScanSafe];
218 [manager setDelegate:nil];
219 } else if (state == CBManagerStatePoweredOff) {
220
221#ifndef Q_OS_MACOS
223 // On iOS a user can see at this point an alert asking to
224 // enable Bluetooth in the "Settings" app. If a user does so,
225 // we'll receive 'PoweredOn' state update later.
226 // No change in internalState. Wait for 30 seconds.
227 [elapsedTimer cancelTimer];
228 elapsedTimer.reset([[DarwinBTGCDTimer alloc] initWithDelegate:self], RetainPolicy::noInitialRetain);
229 [elapsedTimer startWithTimeout:powerOffTimeoutMS step:300];
230 return;
231 }
232#else
233 Q_UNUSED(powerOffTimeoutMS);
234#endif // Q_OS_MACOS
235 [elapsedTimer cancelTimer];
236 [self stopScanSafe];
237 [manager setDelegate:nil];
239 // On macOS we report PoweredOffError and our C++ owner will delete us
240 // (here we're kwnon as 'self'). Connection is Qt::QueuedConnection so we
241 // are apparently safe to call -stopNotifier after the signal.
243 [self stopNotifier];
244 } else {
245 // The following two states we ignore (from Apple's docs):
246 //"
247 // -CBCentralManagerStateUnknown
248 // The current state of the central manager is unknown;
249 // an update is imminent.
250 //
251 // -CBCentralManagerStateResetting
252 // The connection with the system service was momentarily
253 // lost; an update is imminent. "
254 // Wait for this imminent update.
255 }
256
257#pragma clang diagnostic pop
258}
259
261{
262 // CoreBluetooth warns about API misused if we call stopScan in a state
263 // other than powered on. Hence this 'Safe' ...
264 if (!manager)
265 return;
266
267#pragma clang diagnostic push
268#pragma clang diagnostic ignored "-Wunguarded-availability-new"
269
271 const auto state = manager.get().state;
272 if (state == CBManagerStatePoweredOn)
273 [manager stopScan];
274 }
275
276#pragma clang diagnostic pop
277}
278
280{
281 if (notifier) {
284 notifier = nullptr;
285 }
286}
287
288- (void)stop
289{
290 [self stopScanSafe];
291 [manager setDelegate:nil];
292 [elapsedTimer cancelTimer];
293 [self stopNotifier];
295}
296
297- (void)centralManager:(CBCentralManager *)central
298 didDiscoverPeripheral:(CBPeripheral *)peripheral
299 advertisementData:(NSDictionary *)advertisementData
300 RSSI:(NSNumber *)RSSI
301{
302 using namespace DarwinBluetooth;
303
304 if (central != manager)
305 return;
306
308 return;
309
310 if (!notifier)
311 return;
312
314
315 if (!peripheral.identifier) {
316 qCWarning(QT_BT_DARWIN) << "peripheral without NSUUID";
317 return;
318 }
319
321
322 if (deviceUuid.isNull()) {
323 qCWarning(QT_BT_DARWIN) << "no way to address peripheral, QBluetoothUuid is null";
324 return;
325 }
326
327 const AdvertisementData qtAdvData(advertisementData);
328 QString name(qtAdvData.localName);
329 if (!name.size() && peripheral.name)
330 name = QString::fromNSString(peripheral.name);
331
332 // TODO: fix 'classOfDevice' (0 for now).
333 QBluetoothDeviceInfo newDeviceInfo(deviceUuid, name, 0);
334 if (RSSI)
335 newDeviceInfo.setRssi([RSSI shortValue]);
336
337 if (qtAdvData.serviceUuids.size())
338 newDeviceInfo.setServiceUuids(qtAdvData.serviceUuids);
339
340 const QList<quint16> keysManufacturer = qtAdvData.manufacturerData.keys();
341 for (quint16 key : keysManufacturer)
342 newDeviceInfo.setManufacturerData(key, qtAdvData.manufacturerData.value(key));
343
344 const QList<QBluetoothUuid> keysService = qtAdvData.serviceData.keys();
345 for (QBluetoothUuid key : keysService)
346 newDeviceInfo.setServiceData(key, qtAdvData.serviceData.value(key));
347
348 // CoreBluetooth scans only for LE devices.
349 newDeviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
350 emit notifier->deviceDiscovered(newDeviceInfo);
351}
352
353@end
QBluetoothUuid deviceUuid
CBPeripheral * peripheral
DarwinBluetooth::LECBManagerNotifier * notifier
ObjCScopedPointer< CBCentralManager > manager
LEInquiryState internalState
QList< QBluetoothDeviceInfo > devices
int inquiryTimeoutMS
@ InquiryStarting
@ InquiryActive
@ ErrorLENotSupported
@ ErrorNotAuthorized
@ InquiryFinished
@ InquiryCancelled
@ ErrorPoweredOff
ObjCScopedPointer< NSMutableDictionary > advertisementData
void deviceDiscovered(QBluetoothDeviceInfo deviceInfo)
void CBManagerError(QBluetoothDeviceDiscoveryAgent::Error error)
\inmodule QtBluetooth
\inmodule QtBluetooth
\inmodule QtCore
Definition qbytearray.h:57
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1303
QNetworkReply * get(const QNetworkRequest &request)
Posts a request to obtain the contents of the target request and returns a new QNetworkReply object o...
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)
\threadsafe
Definition qobject.cpp:3236
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool isNull() const noexcept
Returns true if this is the null UUID {00000000-0000-0000-0000-000000000000}; otherwise returns false...
Definition quuid.cpp:818
else opt state
[0]
const int defaultLEScanTimeoutMS
Definition btutility.mm:30
const int powerOffTimeoutMS
QBluetoothUuid qt_uuid(NSUUID *nsUuid)
dispatch_queue_t qt_LE_queue()
Definition btutility.mm:324
Combined button and popup list for selecting options.
QString self
Definition language.cpp:58
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qCWarning(category,...)
GLuint64 key
GLenum GLuint id
[7]
GLbitfield GLuint64 timeout
[4]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint name
GLuint GLfloat * val
GLuint in
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define emit
#define Q_UNUSED(x)
unsigned short quint16
Definition qtypes.h:48
QQueue< int > queue
[0]
QNetworkAccessManager manager
QHash< QBluetoothUuid, QByteArray > serviceData
AdvertisementData(NSDictionary *AdvertisementData)
QHash< quint16, QByteArray > manufacturerData
\inmodule QtCore
Definition quuid.h:58