summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.qmake.conf2
-rw-r--r--config.tests/bluez/bluez.pro7
-rw-r--r--config.tests/bluez_le/bluez_le.pro7
-rw-r--r--configure.json5
-rw-r--r--dist/changes-5.8.076
-rw-r--r--examples/bluetooth/heartlistener/heartrate.cpp3
-rw-r--r--examples/bluetooth/lowenergyscanner/device.cpp3
-rw-r--r--qtconnectivity.pro4
-rw-r--r--src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java662
-rw-r--r--src/bluetooth/android/devicediscoverybroadcastreceiver.cpp5
-rw-r--r--src/bluetooth/bluetooth.pro37
-rw-r--r--src/bluetooth/configure.json71
-rw-r--r--src/bluetooth/doc/src/bluetooth-index.qdoc66
-rw-r--r--src/bluetooth/doc/src/bluetooth-le-overview.qdoc4
-rw-r--r--src/bluetooth/osx/osxbt.pri13
-rw-r--r--src/bluetooth/osx/osxbtcentralmanager.mm218
-rw-r--r--src/bluetooth/osx/osxbtcentralmanager_p.h6
-rw-r--r--src/bluetooth/osx/osxbtdeviceinquiry.mm10
-rw-r--r--src/bluetooth/osx/osxbtdevicepair.mm18
-rw-r--r--src/bluetooth/osx/osxbtl2capchannel.mm8
-rw-r--r--src/bluetooth/osx/osxbtledeviceinquiry.mm264
-rw-r--r--src/bluetooth/osx/osxbtledeviceinquiry_p.h30
-rw-r--r--src/bluetooth/osx/osxbtnotifier_p.h15
-rw-r--r--src/bluetooth/osx/osxbtobexsession.mm38
-rw-r--r--src/bluetooth/osx/osxbtperipheralmanager.mm864
-rw-r--r--src/bluetooth/osx/osxbtperipheralmanager_p.h190
-rw-r--r--src/bluetooth/osx/osxbtrfcommchannel.mm8
-rw-r--r--src/bluetooth/osx/osxbtsdpinquiry.mm8
-rw-r--r--src/bluetooth/osx/osxbtutility.mm23
-rw-r--r--src/bluetooth/osx/osxbtutility_p.h23
-rw-r--r--src/bluetooth/qbluetooth.cpp1
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent.cpp115
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent.h17
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp96
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp28
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm192
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm315
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_p.cpp8
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_p.h22
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp647
-rw-r--r--src/bluetooth/qbluetoothdevicediscoverytimer_osx_p.h85
-rw-r--r--src/bluetooth/qbluetoothlocaldevice_android.cpp56
-rw-r--r--src/bluetooth/qbluetoothlocaldevice_osx.mm21
-rw-r--r--src/bluetooth/qbluetoothlocaldevice_p.cpp3
-rw-r--r--src/bluetooth/qbluetoothserver_osx.mm35
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_osx.mm2
-rw-r--r--src/bluetooth/qbluetoothserviceinfo_osx.mm100
-rw-r--r--src/bluetooth/qbluetoothsocket_android.cpp16
-rw-r--r--src/bluetooth/qbluetoothsocket_osx.mm12
-rw-r--r--src/bluetooth/qbluetoothtransferreply_osx.mm13
-rw-r--r--src/bluetooth/qlowenergycontroller.cpp35
-rw-r--r--src/bluetooth/qlowenergycontroller.h1
-rw-r--r--src/bluetooth/qlowenergycontroller_bluez.cpp126
-rw-r--r--src/bluetooth/qlowenergycontroller_osx.mm511
-rw-r--r--src/bluetooth/qlowenergycontroller_osx_p.h29
-rw-r--r--src/bluetooth/qlowenergycontroller_p.h60
-rw-r--r--src/bluetooth/qlowenergycontroller_winrt.cpp1103
-rw-r--r--src/bluetooth/qlowenergyservice.cpp21
-rw-r--r--src/bluetooth/qlowenergyservice_osx.mm6
-rw-r--r--src/imports/bluetooth/plugin.cpp8
-rw-r--r--src/imports/bluetooth/plugins.qmltypes6
-rw-r--r--src/imports/nfc/plugin.cpp6
-rw-r--r--src/imports/nfc/plugins.qmltypes6
-rw-r--r--src/src.pro8
-rw-r--r--src/tools/sdpscanner/qt_attribution.json12
-rw-r--r--src/tools/sdpscanner/sdpscanner.pro4
-rw-r--r--sync.profile12
-rw-r--r--tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro6
-rw-r--r--tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp217
-rw-r--r--tests/auto/qbluetoothsocket/qbluetoothsocket.pro6
-rw-r--r--tests/auto/qlowenergycontroller-gattserver/test/test.pro4
-rw-r--r--tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp21
72 files changed, 5297 insertions, 1383 deletions
diff --git a/.qmake.conf b/.qmake.conf
index a2a0d418..fec66b73 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -1,3 +1,3 @@
load(qt_build_config)
-MODULE_VERSION = 5.7.1
+MODULE_VERSION = 5.8.1
diff --git a/config.tests/bluez/bluez.pro b/config.tests/bluez/bluez.pro
index bb5fa2dd..28dcadcb 100644
--- a/config.tests/bluez/bluez.pro
+++ b/config.tests/bluez/bluez.pro
@@ -1,8 +1 @@
-TEMPLATE = app
-
-CONFIG += link_pkgconfig
-PKGCONFIG += bluez
-
-TARGET = bluez
-
SOURCES += main.cpp
diff --git a/config.tests/bluez_le/bluez_le.pro b/config.tests/bluez_le/bluez_le.pro
index 27b19a45..28dcadcb 100644
--- a/config.tests/bluez_le/bluez_le.pro
+++ b/config.tests/bluez_le/bluez_le.pro
@@ -1,8 +1 @@
-TEMPLATE = app
-
-CONFIG += link_pkgconfig
-PKGCONFIG += bluez
-
-TARGET = bluez_le
-
SOURCES += main.cpp
diff --git a/configure.json b/configure.json
new file mode 100644
index 00000000..e63e6f2a
--- /dev/null
+++ b/configure.json
@@ -0,0 +1,5 @@
+{
+ "subconfigs": [
+ "src/bluetooth"
+ ]
+}
diff --git a/dist/changes-5.8.0 b/dist/changes-5.8.0
new file mode 100644
index 00000000..4e013c13
--- /dev/null
+++ b/dist/changes-5.8.0
@@ -0,0 +1,76 @@
+Qt 5.8 introduces many new features and improvements as well as bugfixes
+over the 5.7.x series. For more details, refer to the online documentation
+included in this distribution. The documentation is also available online:
+
+ http://doc.qt.io/qt-5/index.html
+
+The Qt version 5.8 series is binary compatible with the 5.7.x series.
+Applications compiled for 5.7 will continue to run with 5.8.
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+ https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+* Library *
+****************************************************************************
+
+QtBluetooth
+-----------
+
+ - Added Bluetooth Low Energy peripheral role support for iOS/macOS.
+ - Added WinRT support for QBluetoothDeviceDiscoveryAgent and Bluetooth Low
+ Energy central role.
+ - [QTBUG-53012] Added API to set the timeout of Bluetooth Low Energy device
+ discoveries.
+ - Fixed minor documentation issues.
+ - [QTBUG-46253] Added API to select the device discovery mode utilized by
+ QBluetoothDeviceDiscoveryAgent (btle vs classic vs combined).
+ - [QTBUG-46377] Added preferredSecurityGlags() and setPreferredSecurityFlags()
+ methods to QBluetoothSocket.
+ - Adjusted QtBluetooth to the changes required by the Qt Lite build system.
+
+QtNfc
+-----
+
+ - [QTBUG-55297] Fixed ODR violation in QLlcpServer on Android.
+
+****************************************************************************
+* Platform Specific Changes *
+****************************************************************************
+
+Android
+-------
+
+ - [QTBUG-56625] Parsed list of advertised services found in LE scan
+ record and pass them to API client via QBluetoothDeviceInfo::serviceUuids()
+ - [QTBUG-55035] Added support for new runtime permission check when running Bluetooth
+ applications on Android 7.x. The Bluetooth stack requires Location permissions
+ when running a device discovery.
+ - [QTBUG-52692] Redesigned/Simplified Android central role implementation and prevented
+ blocking of service discovery in case the peripheral does not behave as per standard.
+ - [QTBUG-56625] Improved Low Energy device discovery by evaluating scan records and
+ feeding their content into QBluetoothDeviceInfo instances.
+ - [QTBUG-45066] Fixed crash in QBluetoothDeviceDiscoveryAgent ctor due to Java exception.
+
+iOS/macOS
+--------
+
+ - Removed iOS v 6.x support from the code base
+ - [QTBUG-56898] Fixed a crash when writing GATT attributes.
+ - Adjusted code base to latest macOS and iOS releases.
+ - [QTBUG-53331] Forced error signal when calling
+ QBluetoothLocalDevice::requestPairing().
+ - [QTBUG-52690] Added QLowEnergyController::remoteDeviceUuid() to expose the iOS/macOS
+ specific device UUID. Those two platforms do not expose Bluetooth addresses to
+ application developers.
+
+Linux/Bluez
+-----------
+
+ - Fixed build of sdpscanner due to incorrect build order
+
diff --git a/examples/bluetooth/heartlistener/heartrate.cpp b/examples/bluetooth/heartlistener/heartrate.cpp
index 7077bf1c..c206b69e 100644
--- a/examples/bluetooth/heartlistener/heartrate.cpp
+++ b/examples/bluetooth/heartlistener/heartrate.cpp
@@ -50,6 +50,7 @@ HeartRate::HeartRate():
{
//! [devicediscovery-1]
m_deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
+ m_deviceDiscoveryAgent->setLowEnergyDiscoveryTimeout(5000);
connect(m_deviceDiscoveryAgent, SIGNAL(deviceDiscovered(const QBluetoothDeviceInfo&)),
this, SLOT(addDevice(const QBluetoothDeviceInfo&)));
@@ -73,7 +74,7 @@ void HeartRate::deviceSearch()
qDeleteAll(m_devices);
m_devices.clear();
//! [devicediscovery-2]
- m_deviceDiscoveryAgent->start();
+ m_deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
//! [devicediscovery-2]
setMessage("Scanning for devices...");
}
diff --git a/examples/bluetooth/lowenergyscanner/device.cpp b/examples/bluetooth/lowenergyscanner/device.cpp
index 5ef2cf76..28a051f5 100644
--- a/examples/bluetooth/lowenergyscanner/device.cpp
+++ b/examples/bluetooth/lowenergyscanner/device.cpp
@@ -54,6 +54,7 @@ Device::Device():
{
//! [les-devicediscovery-1]
discoveryAgent = new QBluetoothDeviceDiscoveryAgent();
+ discoveryAgent->setLowEnergyDiscoveryTimeout(5000);
connect(discoveryAgent, SIGNAL(deviceDiscovered(const QBluetoothDeviceInfo&)),
this, SLOT(addDevice(const QBluetoothDeviceInfo&)));
connect(discoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)),
@@ -84,7 +85,7 @@ void Device::startDeviceDiscovery()
setUpdate("Scanning for devices ...");
//! [les-devicediscovery-2]
- discoveryAgent->start();
+ discoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
//! [les-devicediscovery-2]
if (discoveryAgent->isActive()) {
diff --git a/qtconnectivity.pro b/qtconnectivity.pro
index b74830b8..dc0f77e3 100644
--- a/qtconnectivity.pro
+++ b/qtconnectivity.pro
@@ -1,7 +1,3 @@
requires(!android|qtHaveModule(androidextras))
-load(configure)
-qtCompileTest(bluez)
-qtCompileTest(bluez_le)
-qtCompileTest(linux_crypto_api)
load(qt_parts)
diff --git a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java
index 9fe88e9c..5c336dc0 100644
--- a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java
+++ b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java
@@ -48,12 +48,17 @@ import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
import android.util.Log;
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.UUID;
public class QtBluetoothLE {
@@ -65,6 +70,34 @@ public class QtBluetoothLE {
private String mRemoteGattAddress;
private final UUID clientCharacteristicUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+ /*
+ * The atomic synchronizes the timeoutRunnable thread and the response thread for the pending
+ * I/O job. Whichever thread comes first will pass the atomic gate. The other thread is
+ * cut short.
+ */
+ private AtomicInteger handleForTimeout = new AtomicInteger(-1); // -1 implies not running
+ private final int RUNNABLE_TIMEOUT = 3000; // 3 seconds
+ private final Handler timeoutHandler = new Handler(Looper.getMainLooper());
+
+ private class TimeoutRunnable implements Runnable {
+ public TimeoutRunnable(int handle) { pendingJobHandle = handle; }
+ @Override
+ public void run() {
+ boolean timeoutStillValid = handleForTimeout.compareAndSet(pendingJobHandle, -1);
+ if (timeoutStillValid) {
+ Log.w(TAG, "****** Timeout for request on handle " + (pendingJobHandle & 0xffff));
+ Log.w(TAG, "****** Looks like the characteristic or descriptor does NOT act in " +
+ "accordance to Bluetooth 4.x spec.");
+ Log.w(TAG, "****** Please check server implementation. Continuing under " +
+ "reservation.");
+ interruptCurrentIO(pendingJobHandle & 0xffff);
+ }
+ }
+
+ // contains handle (0xffff) and top 2 byte contain the job type (0xffff0000)
+ private int pendingJobHandle = -1;
+ };
+
/* Pointer to the Qt object that "owns" the Java object */
@SuppressWarnings({"CanBeFinal", "WeakerAccess"})
@@ -152,8 +185,12 @@ public class QtBluetoothLE {
switch (status) {
case BluetoothGatt.GATT_SUCCESS:
errorCode = 0; break; //QLowEnergyController::NoError
+ case 8: // link loss
+ case 22: // BTA_GATT_CONN_LMP_TIMEOUT
+ Log.w(TAG, "Connection Error: Try to delay connect() call after previous activity");
+ errorCode = 5; break; //QLowEnergyController::ConnectionError
default:
- Log.w(TAG, "Unhandled error code on connectionStateChanged: " + status);
+ Log.w(TAG, "Unhandled error code on connectionStateChanged: " + status + " " + newState);
errorCode = status; break; //TODO deal with all errors
}
leConnectionStateChange(qtObject, errorCode, qLowEnergyController_State);
@@ -182,64 +219,72 @@ public class QtBluetoothLE {
android.bluetooth.BluetoothGattCharacteristic characteristic,
int status)
{
- //runningHandle is only used during serviceDetailsDiscovery
- //If it is -1 we got an update outside of the details discovery process
- final boolean isServiceDiscoveryRun = (runningHandle != -1);
-
- if (status != BluetoothGatt.GATT_SUCCESS) {
- Log.w(TAG, "onCharacteristicRead error: " + status);
+ int foundHandle = -1;
+ synchronized (this) {
+ foundHandle = handleForCharacteristic(characteristic);
+ if (foundHandle == -1 || foundHandle >= entries.size() ) {
+ Log.w(TAG, "Cannot find characteristic read request for read notification - handle: " +
+ foundHandle + " size: " + entries.size());
+
+ //unlock the queue for next item
+ synchronized (readWriteQueue) {
+ ioJobPending = false;
+ }
- // read errors during serviceDiscovery are ignored
- if (isServiceDiscoveryRun)
+ performNextIO();
return;
+ }
}
- synchronized (this) {
- if (uuidToEntry.isEmpty()) // ignore data if internal setup is not ready;
- return;
+ boolean requestTimedOut = !handleForTimeout.compareAndSet(
+ modifiedReadWriteHandle(foundHandle, IoJobType.Read), -1);
+ if (requestTimedOut) {
+ Log.w(TAG, "Late char read reply after timeout was hit for handle " + foundHandle);
+ // Timeout has hit before this response -> ignore the response
+ // no need to unlock ioJobPending -> the timeout has done that already
+ return;
}
- // once we have a service discovery run we report regular changes
- if (!isServiceDiscoveryRun) {
+ GattEntry entry = entries.get(foundHandle);
+ final boolean isServiceDiscoveryRun = !entry.valueKnown;
+ entry.valueKnown = true;
- int foundHandle = -1;
- synchronized (this) {
- foundHandle = handleForCharacteristic(characteristic);
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ // Qt manages handles starting at 1, in Java we use a system starting with 0
+ //TODO avoid sending service uuid -> service handle should be sufficient
+ leCharacteristicRead(qtObject, characteristic.getService().getUuid().toString(),
+ foundHandle + 1, characteristic.getUuid().toString(),
+ characteristic.getProperties(), characteristic.getValue());
+ } else {
+ if (isServiceDiscoveryRun) {
+ Log.w(TAG, "onCharacteristicRead during discovery error: " + status);
+
+ Log.d(TAG, "Non-readable characteristic " + characteristic.getUuid() +
+ " for service " + characteristic.getService().getUuid());
+ leCharacteristicRead(qtObject, characteristic.getService().getUuid().toString(),
+ foundHandle + 1, characteristic.getUuid().toString(),
+ characteristic.getProperties(), characteristic.getValue());
+ } else {
+ // This must be in sync with QLowEnergyService::CharacteristicReadError
+ final int characteristicReadError = 5;
+ leServiceError(qtObject, foundHandle + 1, characteristicReadError);
}
+ }
- synchronized (readWriteQueue) {
- ioJobPending = false;
- }
+ if (isServiceDiscoveryRun) {
- if (foundHandle == -1) {
- Log.w(TAG, "Out-of-detail-discovery: char update failed. " +
- "Cannot find handle for characteristic");
- } else {
- if (status == BluetoothGatt.GATT_SUCCESS) {
- leCharacteristicRead(qtObject, characteristic.getService().getUuid().toString(),
- foundHandle + 1, characteristic.getUuid().toString(),
- characteristic.getProperties(), characteristic.getValue());
- } else {
- // This must be in sync with QLowEnergyService::CharacteristicReadError
- final int characteristicReadError = 5;
- leServiceError(qtObject, foundHandle + 1, characteristicReadError);
- }
- }
+ // last entry of pending service discovery run -> send discovery finished state update
+ GattEntry serviceEntry = entries.get(entry.associatedServiceHandle);
+ if (serviceEntry.endHandle == foundHandle)
+ finishCurrentServiceDiscovery(entry.associatedServiceHandle);
+ }
- performNextIO();
- return;
+ //unlock the queue for next item
+ synchronized (readWriteQueue) {
+ ioJobPending = false;
}
- GattEntry entry = entries.get(runningHandle);
- entry.valueKnown = true;
- entries.set(runningHandle, entry);
-
- // Qt manages handles starting at 1, in Java we use a system starting with 0
- //TODO avoid sending service uuid -> service handle should be sufficient
- leCharacteristicRead(qtObject, characteristic.getService().getUuid().toString(),
- runningHandle + 1, characteristic.getUuid().toString(),
- characteristic.getProperties(), characteristic.getValue());
- performServiceDetailDiscoveryForHandle(runningHandle + 1, false);
+ performNextIO();
}
public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt,
@@ -255,6 +300,15 @@ public class QtBluetoothLE {
return;
}
+ boolean requestTimedOut = !handleForTimeout.compareAndSet(
+ modifiedReadWriteHandle(handle, IoJobType.Write), -1);
+ if (requestTimedOut) {
+ Log.w(TAG, "Late char write reply after timeout was hit for handle " + handle);
+ // Timeout has hit before this response -> ignore the response
+ // no need to unlock ioJobPending -> the timeout has done that already
+ return;
+ }
+
int errorCode;
//This must be in sync with QLowEnergyService::ServiceError
switch (status) {
@@ -287,81 +341,84 @@ public class QtBluetoothLE {
android.bluetooth.BluetoothGattDescriptor descriptor,
int status)
{
- //runningHandle is only used during serviceDetailsDiscovery
- //If it is -1 we got an update outside of the details discovery process
- final boolean isServiceDiscoveryRun = (runningHandle != -1);
-
- if (status != BluetoothGatt.GATT_SUCCESS) {
- Log.w(TAG, "onDescriptorRead error: " + status);
-
- // read errors during serviceDiscovery are ignored
- if (isServiceDiscoveryRun)
- return;
- }
-
+ int foundHandle = -1;
synchronized (this) {
- if (uuidToEntry.isEmpty()) // ignore data if internal setup is not ready;
+ foundHandle = handleForDescriptor(descriptor);
+ if (foundHandle == -1 || foundHandle >= entries.size() ) {
+ Log.w(TAG, "Cannot find descriptor read request for read notification - handle: " +
+ foundHandle + " size: " + entries.size());
+
+ //unlock the queue for next item
+ synchronized (readWriteQueue) {
+ ioJobPending = false;
+ }
+ performNextIO();
return;
+ }
}
+ boolean requestTimedOut = !handleForTimeout.compareAndSet(
+ modifiedReadWriteHandle(foundHandle, IoJobType.Read), -1);
+ if (requestTimedOut) {
+ Log.w(TAG, "Late descriptor read reply after timeout was hit for handle " +
+ foundHandle);
+ // Timeout has hit before this response -> ignore the response
+ // no need to unlock ioJobPending -> the timeout has done that already
+ return;
+ }
- if (!isServiceDiscoveryRun) {
+ GattEntry entry = entries.get(foundHandle);
+ final boolean isServiceDiscoveryRun = !entry.valueKnown;
+ entry.valueKnown = true;
- int foundHandle = -1;
- synchronized (this) {
- foundHandle = handleForDescriptor(descriptor);
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ //TODO avoid sending service and characteristic uuid -> handles should be sufficient
+ leDescriptorRead(qtObject, descriptor.getCharacteristic().getService().getUuid().toString(),
+ descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1,
+ descriptor.getUuid().toString(), descriptor.getValue());
+ } else {
+ if (isServiceDiscoveryRun) {
+ //ignore
+ Log.w(TAG, "onDescriptorcRead during discovery error: " + status);
+ } else {
+ // This must be in sync with QLowEnergyService::DescriptorReadError
+ final int descriptorReadError = 6;
+ leServiceError(qtObject, foundHandle + 1, descriptorReadError);
}
- synchronized (readWriteQueue) {
- ioJobPending = false;
+ }
+
+ if (isServiceDiscoveryRun) {
+ // last entry of pending service discovery run? ->send discovery finished state update
+ GattEntry serviceEntry = entries.get(entry.associatedServiceHandle);
+ if (serviceEntry.endHandle == foundHandle) {
+ finishCurrentServiceDiscovery(entry.associatedServiceHandle);
}
- if (foundHandle == -1) {
- Log.w(TAG, "Out-of-detail-discovery: char update failed. " +
- "Cannot find handle for descriptor.");
- } else {
- if (status == BluetoothGatt.GATT_SUCCESS) {
- leDescriptorRead(qtObject, descriptor.getCharacteristic().getService().getUuid().toString(),
- descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1,
- descriptor.getUuid().toString(), descriptor.getValue());
- } else {
- // This must be in sync with QLowEnergyService::DescriptorReadError
- final int descriptorReadError = 6;
- leServiceError(qtObject, foundHandle + 1, descriptorReadError);
+ /* Some devices preset ClientCharacteristicConfiguration descriptors
+ * to enable notifications out of the box. However the additional
+ * BluetoothGatt.setCharacteristicNotification call prevents
+ * automatic notifications from coming through. Hence we manually set them
+ * up here.
+ */
+ if (descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) {
+ byte[] bytearray = descriptor.getValue();
+ final int value = (bytearray != null && bytearray.length > 0) ? bytearray[0] : 0;
+ // notification or indication bit set?
+ if ((value & 0x03) > 0) {
+ Log.d(TAG, "Found descriptor with automatic notifications.");
+ mBluetoothGatt.setCharacteristicNotification(
+ descriptor.getCharacteristic(), true);
}
}
-
- performNextIO();
- return;
}
-
- GattEntry entry = entries.get(runningHandle);
- entry.valueKnown = true;
- entries.set(runningHandle, entry);
- //TODO avoid sending service and characteristic uuid -> handles should be sufficient
- leDescriptorRead(qtObject, descriptor.getCharacteristic().getService().getUuid().toString(),
- descriptor.getCharacteristic().getUuid().toString(), runningHandle+1,
- descriptor.getUuid().toString(), descriptor.getValue());
-
- /* Some devices preset ClientCharacteristicConfiguration descriptors
- * to enable notifications out of the box. However the additional
- * BluetoothGatt.setCharacteristicNotification call prevents
- * automatic notifications from coming through. Hence we manually set them
- * up here.
- */
-
- if (descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) {
- final int value = descriptor.getValue()[0];
- // notification or indication bit set?
- if ((value & 0x03) > 0) {
- Log.d(TAG, "Found descriptor with automatic notifications.");
- mBluetoothGatt.setCharacteristicNotification(
- descriptor.getCharacteristic(), true);
- }
+ //unlock the queue for next item
+ synchronized (readWriteQueue) {
+ ioJobPending = false;
}
- performServiceDetailDiscoveryForHandle(runningHandle + 1, false);
+ performNextIO();
}
public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt,
@@ -373,6 +430,16 @@ public class QtBluetoothLE {
int handle = handleForDescriptor(descriptor);
+ boolean requestTimedOut = !handleForTimeout.compareAndSet(
+ modifiedReadWriteHandle(handle, IoJobType.Write), -1);
+ if (requestTimedOut) {
+ Log.w(TAG, "Late descriptor write reply after timeout was hit for handle " +
+ handle);
+ // Timeout has hit before this response -> ignore the response
+ // no need to unlock ioJobPending -> the timeout has done that already
+ return;
+ }
+
int errorCode;
//This must be in sync with QLowEnergyService::ServiceError
switch (status) {
@@ -413,7 +480,31 @@ public class QtBluetoothLE {
return false;
}
- mBluetoothGatt = mRemoteGattDevice.connectGatt(qtContext, false, gattCallback);
+ try {
+ // BluetoothDevice.,connectGatt(Context, boolean, BluetoothGattCallback, int) was
+ // officially introduced by Android API v23. Earlier Android versions have a private
+ // implementation already though. Let's check at runtime and use it if possible.
+ //
+ // In general the new connectGatt() seems to be much more reliable than the function
+ // that doesn't specify the transport layer.
+
+ Class[] args = new Class[4];
+ args[0] = android.content.Context.class;
+ args[1] = boolean.class;
+ args[2] = android.bluetooth.BluetoothGattCallback.class;
+ args[3] = int.class;
+ Method connectMethod = mRemoteGattDevice.getClass().getDeclaredMethod("connectGatt", args);
+ if (connectMethod != null) {
+ mBluetoothGatt = (BluetoothGatt) connectMethod.invoke(mRemoteGattDevice, qtContext,
+ false, gattCallback,
+ 2 /*TRANSPORT_LE*/);
+ Log.w(TAG, "Using Android v23 BluetoothDevice.connectGatt()");
+ }
+ } catch (Exception ex) {
+ // fallback to less reliable API 18 version
+ mBluetoothGatt = mRemoteGattDevice.connectGatt(qtContext, false, gattCallback);
+ }
+
return mBluetoothGatt != null;
}
@@ -440,7 +531,15 @@ public class QtBluetoothLE {
public BluetoothGattService service = null;
public BluetoothGattCharacteristic characteristic = null;
public BluetoothGattDescriptor descriptor = null;
- public int endHandle;
+ /*
+ * endHandle defined for GattEntryType.Service and GattEntryType.CharacteristicValue
+ * If the type is service this is the value of the last Gatt entry belonging to the very
+ * same service. If the type is a char value it is the entries index inside
+ * the "entries" list.
+ */
+ public int endHandle = -1;
+ // pointer back to the handle that describes the service that this GATT entry belongs to
+ public int associatedServiceHandle;
}
private enum IoJobType
@@ -456,8 +555,12 @@ public class QtBluetoothLE {
public IoJobType jobType;
}
+ // service uuid -> service handle mapping (there can be more than one service with same uuid)
private final Hashtable<UUID, List<Integer>> uuidToEntry = new Hashtable<UUID, List<Integer>>(100);
+ // index into array is equivalent to handle id
private final ArrayList<GattEntry> entries = new ArrayList<GattEntry>(100);
+ //backlog of to be discovered services
+ // TODO remove
private final LinkedList<Integer> servicesToBeDiscovered = new LinkedList<Integer>();
@@ -557,10 +660,12 @@ public class QtBluetoothLE {
GattEntry serviceEntry = new GattEntry();
serviceEntry.type = GattEntryType.Service;
serviceEntry.service = service;
- entries.add(entry);
+ entries.add(serviceEntry);
// remember handle for the service for later update
int serviceHandle = entries.size() - 1;
+ //point to itself -> mostly done for consistence reasons with other entries
+ serviceEntry.associatedServiceHandle = serviceHandle;
//some devices may have more than one service with the same uuid
List<Integer> old = uuidToEntry.get(service.getUuid());
@@ -575,11 +680,15 @@ public class QtBluetoothLE {
entry = new GattEntry();
entry.type = GattEntryType.Characteristic;
entry.characteristic = characteristic;
+ entry.associatedServiceHandle = serviceHandle;
+ //entry.endHandle = .. undefined
entries.add(entry);
// this emulates GATT value attributes
entry = new GattEntry();
entry.type = GattEntryType.CharacteristicValue;
+ entry.associatedServiceHandle = serviceHandle;
+ entry.endHandle = entries.size(); // special case -> current index in entries list
entries.add(entry);
// add all descriptors
@@ -588,30 +697,31 @@ public class QtBluetoothLE {
entry = new GattEntry();
entry.type = GattEntryType.Descriptor;
entry.descriptor = desc;
+ entry.associatedServiceHandle = serviceHandle;
+ //entry.endHandle = .. undefined
entries.add(entry);
}
}
// update endHandle of current service
serviceEntry.endHandle = entries.size() - 1;
- entries.set(serviceHandle, serviceEntry);
}
entries.trimToSize();
}
- private int currentServiceInDiscovery = -1;
- private int runningHandle = -1;
-
private void resetData()
{
synchronized (this) {
- runningHandle = -1;
- currentServiceInDiscovery = -1;
uuidToEntry.clear();
entries.clear();
servicesToBeDiscovered.clear();
}
+
+ // kill all timeout handlers
+ timeoutHandler.removeCallbacksAndMessages(null);
+ handleForTimeout.set(-1);
+
synchronized (readWriteQueue) {
readWriteQueue.clear();
}
@@ -654,28 +764,15 @@ public class QtBluetoothLE {
return false;
}
- // current service already under investigation
- if (currentServiceInDiscovery == serviceHandle)
- return true;
-
- if (currentServiceInDiscovery != -1) {
- // we are currently discovering another service
- // we queue the new one up until we finish the previous one
- if (!entry.valueKnown) {
- servicesToBeDiscovered.add(serviceHandle);
- Log.w(TAG, "Service discovery already running on another service, " +
- "queueing request for " + serviceUuid);
- } else {
- Log.w(TAG, "Service already known");
- }
+ // current service already discovered or under investigation
+ if (entry.valueKnown || servicesToBeDiscovered.contains(serviceHandle)) {
+ Log.w(TAG, "Service already known or to be discovered");
return true;
}
- if (!entry.valueKnown) {
- performServiceDetailDiscoveryForHandle(serviceHandle, true);
- } else {
- Log.w(TAG, "Service already discovered");
- }
+ servicesToBeDiscovered.add(serviceHandle);
+ scheduleServiceDetailDiscovery(serviceHandle);
+ performNextIO();
} catch (Exception ex) {
ex.printStackTrace();
@@ -716,98 +813,76 @@ public class QtBluetoothLE {
return builder.toString();
}
- private void finishCurrentServiceDiscovery()
+ //TODO function not yet used
+ private void finishCurrentServiceDiscovery(int handleDiscoveredService)
{
- int currentEntry = currentServiceInDiscovery;
- GattEntry discoveredService = entries.get(currentServiceInDiscovery);
+ Log.w(TAG, "Finished current discovery for service handle " + handleDiscoveredService);
+ GattEntry discoveredService = entries.get(handleDiscoveredService);
discoveredService.valueKnown = true;
- entries.set(currentServiceInDiscovery, discoveredService);
-
- runningHandle = -1;
- currentServiceInDiscovery = -1;
-
- leServiceDetailDiscoveryFinished(qtObject, discoveredService.service.getUuid().toString(),
- currentEntry + 1, discoveredService.endHandle + 1);
-
- if (!servicesToBeDiscovered.isEmpty()) {
+ synchronized (this) {
try {
- int nextService = servicesToBeDiscovered.remove();
- performServiceDetailDiscoveryForHandle(nextService, true);
- } catch (IndexOutOfBoundsException ex) {
+ servicesToBeDiscovered.removeFirst();
+ } catch (NoSuchElementException ex) {
Log.w(TAG, "Expected queued service but didn't find any");
}
}
+
+ leServiceDetailDiscoveryFinished(qtObject, discoveredService.service.getUuid().toString(),
+ handleDiscoveredService + 1, discoveredService.endHandle + 1);
}
- private synchronized void performServiceDetailDiscoveryForHandle(int nextHandle, boolean searchStarted)
+ /*
+ Internal Helper function for discoverServiceDetails()
+
+ Adds all Gatt entries for the given service to the readWriteQueue to be discovered.
+ This function only ever adds read requests to the queue.
+
+ //TODO function not yet used
+ */
+ private void scheduleServiceDetailDiscovery(int serviceHandle)
{
- try {
- if (searchStarted) {
- currentServiceInDiscovery = nextHandle;
- runningHandle = ++nextHandle;
- } else {
- runningHandle = nextHandle;
- }
+ GattEntry serviceEntry = entries.get(serviceHandle);
+ final int endHandle = serviceEntry.endHandle;
- GattEntry entry;
- try {
- entry = entries.get(nextHandle);
- } catch (IndexOutOfBoundsException ex) {
- //ex.printStackTrace();
- Log.w(TAG, "Last entry of last service read");
- finishCurrentServiceDiscovery();
- return;
- }
+ if (serviceHandle == endHandle) {
+ Log.w(TAG, "scheduleServiceDetailDiscovery: service is empty; nothing to discover");
+ finishCurrentServiceDiscovery(serviceHandle);
+ return;
+ }
- boolean result;
- switch (entry.type) {
- case Characteristic:
- result = mBluetoothGatt.readCharacteristic(entry.characteristic);
- try {
- if (!result) {
- // add characteristic now since we won't get a read update later one
- // this is possible when the characteristic is not readable
- Log.d(TAG, "Non-readable characteristic " + entry.characteristic.getUuid() +
- " for service " + entry.characteristic.getService().getUuid());
- leCharacteristicRead(qtObject, entry.characteristic.getService().getUuid().toString(),
- nextHandle + 1, entry.characteristic.getUuid().toString(),
- entry.characteristic.getProperties(), entry.characteristic.getValue());
- performServiceDetailDiscoveryForHandle(runningHandle + 1, false);
- }
- } catch (Exception ex)
- {
- ex.printStackTrace();
- }
- break;
- case CharacteristicValue:
- // ignore -> nothing to do for this artificial type
- performServiceDetailDiscoveryForHandle(runningHandle + 1, false);
- break;
- case Descriptor:
- result = mBluetoothGatt.readDescriptor(entry.descriptor);
- if (!result) {
- // atm all descriptor types are readable
- Log.d(TAG, "Non-readable descriptor " + entry.descriptor.getUuid() +
- " for service/char" + entry.descriptor.getCharacteristic().getService().getUuid() +
- "/" + entry.descriptor.getCharacteristic().getUuid());
- leDescriptorRead(qtObject,
- entry.descriptor.getCharacteristic().getService().getUuid().toString(),
- entry.descriptor.getCharacteristic().getUuid().toString(),
- nextHandle+1, entry.descriptor.getUuid().toString(),
- entry.descriptor.getValue());
- performServiceDetailDiscoveryForHandle(runningHandle + 1, false);
- }
- break;
- case Service:
- finishCurrentServiceDiscovery();
- break;
- default:
- Log.w(TAG, "Invalid GATT attribute type");
- break;
- }
+ synchronized (readWriteQueue) {
+ // entire block inside mutex to ensure all service discovery jobs go in one after the other
+ // ensures that serviceDiscovered() signal is sent when required
- } catch(Exception ex) {
- ex.printStackTrace();
+
+ // serviceHandle + 1 -> ignore service handle itself
+ for (int i = serviceHandle + 1; i <= endHandle; i++) {
+ GattEntry entry = entries.get(i);
+
+ switch (entry.type) {
+ case Characteristic:
+ case Descriptor:
+ // we schedule CharacteristicValue for initial discovery to simplify
+ // detection of the end of service discovery process
+ // performNextIO() ignores CharacteristicValue GATT entries
+ case CharacteristicValue:
+ break;
+ case Service:
+ // should not really happen unless endHandle is wrong
+ Log.w(TAG, "scheduleServiceDetailDiscovery: wrong endHandle");
+ return;
+ }
+
+ // only descriptor and characteristic fall through to this point
+ ReadWriteJob newJob = new ReadWriteJob();
+ newJob.entry = entry;
+ newJob.jobType = IoJobType.Read;
+
+ final boolean result = readWriteQueue.add(newJob);
+ if (!result)
+ Log.w(TAG, "Cannot add service discovery job for " + serviceEntry.service.getUuid()
+ + " on item " + entry.type);
+ }
}
}
@@ -964,6 +1039,30 @@ public class QtBluetoothLE {
return true;
}
+ // Called by TimeoutRunnable if the current I/O job timeoutd out.
+ // By the time we reach this point the handleForTimeout counter has already been reset
+ // and the regular responses will be blocked off.
+ private void interruptCurrentIO(int handle)
+ {
+ //unlock the queue for next item
+ synchronized (readWriteQueue) {
+ ioJobPending = false;
+ }
+
+ performNextIO();
+
+ GattEntry entry = entries.get(handle);
+ if (entry == null)
+ return;
+ if (entry.valueKnown)
+ return;
+ entry.valueKnown = true;
+
+ GattEntry serviceEntry = entries.get(entry.associatedServiceHandle);
+ if (serviceEntry != null && serviceEntry.endHandle == handle)
+ finishCurrentServiceDiscovery(entry.associatedServiceHandle);
+ }
+
/*
The queuing is required because two writeCharacteristic/writeDescriptor calls
cannot execute at the same time. The second write must happen after the
@@ -976,50 +1075,111 @@ public class QtBluetoothLE {
boolean skip = false;
final ReadWriteJob nextJob;
+ int handle = -1;
+
synchronized (readWriteQueue) {
if (readWriteQueue.isEmpty() || ioJobPending)
return;
nextJob = readWriteQueue.remove();
+ switch (nextJob.entry.type) {
+ case Characteristic:
+ handle = handleForCharacteristic(nextJob.entry.characteristic);
+ break;
+ case Descriptor:
+ handle = handleForDescriptor(nextJob.entry.descriptor);
+ break;
+ case CharacteristicValue:
+ handle = nextJob.entry.endHandle;
+ default:
+ break;
+ }
+
+ // timeout handler and handleForTimeout atomic must be setup before
+ // executing the request. Sometimes the callback is quicker than executing the
+ // remainder of this function. Therefore enable the atomic early such that
+ // callback handlers start hanging in the readWriteQueue sync block which
+ // we are still occupying here.
+ timeoutHandler.removeCallbacksAndMessages(null); // remove any timeout handlers
+ handleForTimeout.set(modifiedReadWriteHandle(handle, nextJob.jobType));
- Log.w(TAG, "Performing queued job " + nextJob.jobType);
if (nextJob.jobType == IoJobType.Read)
skip = executeReadJob(nextJob);
else
skip = executeWriteJob(nextJob);
- if (!skip)
+ if (skip) {
+ handleForTimeout.set(-1); // not a pending call -> release atomic
+ } else {
ioJobPending = true;
+ timeoutHandler.postDelayed(new TimeoutRunnable(
+ modifiedReadWriteHandle(handle, nextJob.jobType)), RUNNABLE_TIMEOUT);
+ }
+
+ Log.w(TAG, "Performing queued job, handle: " + handle + " " + nextJob.jobType + " (" +
+ (nextJob.requestedWriteType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) +
+ ") ValueKnown: " + nextJob.entry.valueKnown + " Skipping: " + skip +
+ " " + nextJob.entry.type);
}
- if (skip) {
- Log.w(TAG, "Skipping: " + nextJob.entry.type);
+ GattEntry entry = nextJob.entry;
+ if (skip) {
/*
BluetoothGatt.[read|write][Characteristic|Descriptor]() immediately
return in cases where meta data doesn't match the intended action
(e.g. trying to write to read-only char). When this happens
- we have to report an error back to Qt. This is not required during
+ we have to report an error back to Qt. The error report is not required during
the initial service discovery though.
*/
- final boolean isServiceDiscoveryRun = (runningHandle != -1);
- if (!isServiceDiscoveryRun) {
- int handle = -1;
- if (nextJob.entry.type == GattEntryType.Characteristic)
- handle = handleForCharacteristic(nextJob.entry.characteristic);
- else
- handle = handleForDescriptor(nextJob.entry.descriptor);
+ if (handle != -1) {
+ // during service discovery we do not report error but emit characteristicRead()
+ // any other time a failure emits serviceError() signal
+
+ final boolean isServiceDiscovery = !entry.valueKnown;
- if (handle != -1) {
+ if (isServiceDiscovery) {
+ entry.valueKnown = true;
+ switch (entry.type) {
+ case Characteristic:
+ Log.d(TAG, "Non-readable characteristic " + entry.characteristic.getUuid() +
+ " for service " + entry.characteristic.getService().getUuid());
+ leCharacteristicRead(qtObject, entry.characteristic.getService().getUuid().toString(),
+ handle + 1, entry.characteristic.getUuid().toString(),
+ entry.characteristic.getProperties(), entry.characteristic.getValue());
+ break;
+ case Descriptor:
+ // atm all descriptor types are readable
+ Log.d(TAG, "Non-readable descriptor " + entry.descriptor.getUuid() +
+ " for service/char" + entry.descriptor.getCharacteristic().getService().getUuid() +
+ "/" + entry.descriptor.getCharacteristic().getUuid());
+ leDescriptorRead(qtObject,
+ entry.descriptor.getCharacteristic().getService().getUuid().toString(),
+ entry.descriptor.getCharacteristic().getUuid().toString(),
+ handle + 1, entry.descriptor.getUuid().toString(),
+ entry.descriptor.getValue());
+ break;
+ case CharacteristicValue:
+ // for more details see scheduleServiceDetailDiscovery(int)
+ // ignore and continue unless last entry
+ GattEntry serviceEntry = entries.get(entry.associatedServiceHandle);
+ if (serviceEntry.endHandle == handle)
+ finishCurrentServiceDiscovery(entry.associatedServiceHandle);
+ break;
+ case Service:
+ Log.w(TAG, "Scheduling of Service Gatt entry for service discovery should never happen.");
+ break;
+ }
+ } else {
int errorCode = 0;
// The error codes below must be in sync with QLowEnergyService::ServiceError
if (nextJob.jobType == IoJobType.Read) {
- errorCode = (nextJob.entry.type == GattEntryType.Characteristic) ?
- 5 : 6; // CharacteristicReadError : DescriptorReadError
+ errorCode = (entry.type == GattEntryType.Characteristic) ?
+ 5 : 6; // CharacteristicReadError : DescriptorReadError
} else {
- errorCode = (nextJob.entry.type == GattEntryType.Characteristic) ?
- 2 : 3; // CharacteristicWriteError : DescriptorWriteError
+ errorCode = (entry.type == GattEntryType.Characteristic) ?
+ 2 : 3; // CharacteristicWriteError : DescriptorWriteError
}
leServiceError(qtObject, handle + 1, errorCode);
@@ -1109,12 +1269,52 @@ public class QtBluetoothLE {
return true; // skip
break;
case Service:
- case CharacteristicValue:
return true;
+ case CharacteristicValue:
+ return true; //skip
}
return false;
}
+ /*
+ * Modifies and returns the given \a handle such that the job
+ * \a type is encoded into the returned handle. Hereby we take advantage of the fact that
+ * a Bluetooth Low Energy handle is only 16 bit. The handle will be the bottom two bytes
+ * and the job type will be in the top 2 bytes.
+ *
+ * top 2 bytes
+ * - 0x01 -> Read Job
+ * - 0x02 -> Write Job
+ *
+ * This is done in connection with handleForTimeout and assists in the process of
+ * detecting accidental interruption by the timeout handler.
+ * If two requests for the same handle are scheduled behind each other there is the
+ * theoretical chance that the first request comes back normally while the second request
+ * is interrupted by the timeout handler. This risk still exists but this function ensures that
+ * at least back to back requests of differing types cannot affect each other via the timeout
+ * handler.
+ */
+ private int modifiedReadWriteHandle(int handle, IoJobType type)
+ {
+ int modifiedHandle = handle;
+ // ensure we have 16bit handle only
+ if (handle > 0xFFFF)
+ Log.w(TAG, "Invalid handle");
+
+ modifiedHandle = (modifiedHandle & 0xFFFF);
+
+ switch (type) {
+ case Write:
+ modifiedHandle = (modifiedHandle | 0x00010000);
+ break;
+ case Read:
+ modifiedHandle = (modifiedHandle | 0x00020000);
+ break;
+ }
+
+ return modifiedHandle;
+ }
+
public native void leConnectionStateChange(long qtObject, int wasErrorTransition, int newState);
public native void leServicesDiscovered(long qtObject, int errorCode, String uuidList);
public native void leServiceDetailDiscoveryFinished(long qtObject, final String serviceUuid,
diff --git a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
index c807df7f..9c9c0409 100644
--- a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
+++ b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
@@ -511,7 +511,8 @@ QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(JNIEnv
if (scanRecord != nullptr) {
// Parse scan record
jboolean isCopy;
- const char *scanRecordBuffer = reinterpret_cast<const char *>(env->GetByteArrayElements(scanRecord, &isCopy));
+ jbyte *elems = env->GetByteArrayElements(scanRecord, &isCopy);
+ const char *scanRecordBuffer = reinterpret_cast<const char *>(elems);
const int scanRecordLength = env->GetArrayLength(scanRecord);
QList<QBluetoothUuid> serviceUuids;
@@ -560,6 +561,8 @@ QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(JNIEnv
}
info.setServiceUuids(serviceUuids, QBluetoothDeviceInfo::DataIncomplete);
+
+ env->ReleaseByteArrayElements(scanRecord, elems, JNI_ABORT);
}
if (QtAndroidPrivate::androidSdkVersion() >= 18) {
diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro
index 1857e983..bf2dce74 100644
--- a/src/bluetooth/bluetooth.pro
+++ b/src/bluetooth/bluetooth.pro
@@ -78,7 +78,7 @@ SOURCES += \
qlowenergycontroller.cpp \
qlowenergyserviceprivate.cpp
-config_bluez:qtHaveModule(dbus) {
+qtConfig(bluez):qtHaveModule(dbus) {
QT_FOR_PRIVATE += dbus
DEFINES += QT_BLUEZ_BLUETOOTH
@@ -98,16 +98,13 @@ config_bluez:qtHaveModule(dbus) {
# old versions of Bluez do not have the required BTLE symbols
- config_bluez_le {
+ qtConfig(bluez_le) {
SOURCES += \
qleadvertiser_bluez.cpp \
qlowenergycontroller_bluez.cpp \
lecmaccalculator.cpp
- config_linux_crypto_api:DEFINES += CONFIG_LINUX_CRYPTO_API
- else:message("Linux crypto API not present, signed writes will not work.")
+ qtConfig(linux_crypto_api): DEFINES += CONFIG_LINUX_CRYPTO_API
} else {
- message("Bluez version is too old to support Bluetooth Low Energy.")
- message("Only classic Bluetooth will be available.")
DEFINES += QT_BLUEZ_NO_BTLE
include(dummy/dummy.pri)
SOURCES += \
@@ -153,14 +150,10 @@ config_bluez:qtHaveModule(dbus) {
qlowenergycontroller_osx.mm \
qlowenergyservice_osx.mm
- SOURCES += \
- qlowenergycontroller_p.cpp
-
PRIVATE_HEADERS += qbluetoothsocket_osx_p.h \
qbluetoothserver_osx_p.h \
qbluetoothtransferreply_osx_p.h \
qbluetoothtransferreply_osx_p.h \
- qbluetoothdevicediscoverytimer_osx_p.h \
qlowenergycontroller_osx_p.h
SOURCES -= qbluetoothdevicediscoveryagent.cpp
@@ -182,8 +175,7 @@ config_bluez:qtHaveModule(dbus) {
qlowenergyservice_osx.mm
PRIVATE_HEADERS += \
- qlowenergycontroller_osx_p.h \
- qbluetoothdevicediscoverytimer_osx_p.h
+ qlowenergycontroller_osx_p.h
include(osx/osxbt.pri)
SOURCES += \
@@ -198,6 +190,21 @@ config_bluez:qtHaveModule(dbus) {
SOURCES -= qlowenergycontroller_p.cpp
SOURCES -= qlowenergyservice.cpp
SOURCES -= qlowenergycontroller.cpp
+} else:if(winphone|winrt-*-msvc2015) {
+ DEFINES += QT_WINRT_BLUETOOTH
+ QT += core-private
+
+ # remove dummy warning once platform port is complete
+ include(dummy/dummy.pri)
+
+ SOURCES += \
+ qbluetoothdevicediscoveryagent_winrt.cpp \
+ qbluetoothlocaldevice_p.cpp \
+ qbluetoothserver_p.cpp \
+ qbluetoothservicediscoveryagent_p.cpp \
+ qbluetoothserviceinfo_p.cpp \
+ qbluetoothsocket_p.cpp \
+ qlowenergycontroller_winrt.cpp
} else {
message("Unsupported Bluetooth platform, will not build a working QtBluetooth library.")
message("Either no Qt D-Bus found or no BlueZ headers available.")
@@ -212,6 +219,12 @@ config_bluez:qtHaveModule(dbus) {
qlowenergycontroller_p.cpp
}
+winrt-*-msvc2015 {
+ MODULE_WINRT_CAPABILITIES_DEVICE += \
+ bluetooth.genericAttributeProfile \
+ bluetooth.rfcomm
+}
+
OTHER_FILES +=
HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS
diff --git a/src/bluetooth/configure.json b/src/bluetooth/configure.json
new file mode 100644
index 00000000..1cf43f36
--- /dev/null
+++ b/src/bluetooth/configure.json
@@ -0,0 +1,71 @@
+{
+ "module": "bluetooth",
+ "testDir": "../../config.tests",
+
+ "libraries": {
+ "bluez": {
+ "label": "BlueZ",
+ "test": "bluez",
+ "sources": [
+ { "type": "pkgConfig", "args": "bluez" },
+ "-lbluetooth"
+ ]
+ }
+ },
+
+ "tests": {
+ "bluez_le": {
+ "label": "BlueZ Low Energy",
+ "type": "compile",
+ "test": "bluez_le"
+ },
+ "linux_crypto_api": {
+ "label": "Linux Crypto API",
+ "type": "compile",
+ "test": "linux_crypto_api"
+ }
+ },
+
+ "features": {
+ "bluez": {
+ "label": "BlueZ",
+ "condition": "libs.bluez",
+ "output": [ "privateFeature" ]
+ },
+ "bluez_le": {
+ "label": "BlueZ Low Energy",
+ "condition": "features.bluez && tests.bluez_le",
+ "output": [ "privateFeature" ]
+ },
+ "linux_crypto_api": {
+ "label": "Linux Crypto API",
+ "condition": "features.bluez_le && tests.linux_crypto_api",
+ "output": [ "privateFeature" ]
+ }
+ },
+
+ "report": [
+ {
+ "type": "note",
+ "condition": "features.bluez_le && !features.linux_crypto_api",
+ "message": "Linux crypto API not present. BTLE signed writes will not work."
+ },
+ {
+ "type": "note",
+ "condition": "features.bluez && !features.bluez_le",
+ "message": "Bluez version is too old to support Bluetooth Low Energy.
+Only classic Bluetooth will be available."
+ }
+ ],
+
+ "summary": [
+ {
+ "section": "Qt Bluetooth",
+ "entries": [
+ "bluez",
+ "bluez_le",
+ "linux_crypto_api"
+ ]
+ }
+ ]
+}
diff --git a/src/bluetooth/doc/src/bluetooth-index.qdoc b/src/bluetooth/doc/src/bluetooth-index.qdoc
index b168cd2d..92b846b4 100644
--- a/src/bluetooth/doc/src/bluetooth-index.qdoc
+++ b/src/bluetooth/doc/src/bluetooth-index.qdoc
@@ -33,9 +33,50 @@
The Bluetooth API provides connectivity between Bluetooth enabled devices.
-Currently, the API is supported on the following platforms: \l{Qt for Android}{Android},
-\l{Qt for iOS}{iOS}, \l{Qt for Linux/X11}{Linux}
-(\l{http://www.bluez.org}{BlueZ 4.x/5.x}) and \l{Qt for macOS}{\macos}.
+Currently, the API is supported on the following platforms:
+
+\table
+\header
+ \li API Feature
+ \li \l {Qt for Android}{Android}
+ \li \l {Qt for iOS}{iOS}
+ \li \l {Qt for Linux/X11}{Linux (BlueZ 4.x/5.x)}
+ \li \l {Qt for OS X}{macOS}
+ \li \l {Qt for WinRT}{WinRT}
+ \li \l {Qt for Windows}{Windows}
+\row
+ \li Classic Bluetooth
+ \li x
+ \li x
+ \li x
+ \li x
+ \li
+ \li
+\row
+ \li Bluetooth LE Central
+ \li x
+ \li x
+ \li x
+ \li x
+ \li x
+ \li
+\row
+ \li Bluetooth LE Peripheral
+ \li
+ \li x
+ \li x
+ \li x
+ \li
+ \li
+\row
+ \li Bluetooth LE Advertisement & Scanning
+ \li
+ \li
+ \li
+ \li
+ \li
+ \li
+\endtable
\section1 Overview
@@ -49,8 +90,8 @@ Qt Bluetooth supports Bluetooth Low Energy development for client/central role u
Further details can be found in the
\l {Bluetooth Low Energy Overview}{Bluetooth Low Energy Overview} section.
-A new addition in this Qt Bluetooth 5.7 release covers support for Bluetooth Low Energy
-applications performing the peripheral/server role. This new API is a Technology Preview.
+A new addition since the Qt Bluetooth 5.7 release covers support for Bluetooth Low Energy
+applications performing the peripheral/server role. This new API remains in Technology Preview.
\section1 Getting Started
@@ -136,4 +177,19 @@ The \l QtBluetooth module exports the following
\li \l {btfiletransfer}{Bluetooth File Transfer}
\endlist
\endlist
+
+\section1 Licenses and Attributions
+
+Qt Bluetooth is available under commercial licenses from \l{The Qt Company}.
+In addition, it is available under the
+\l{GNU Lesser General Public License, version 3}, or
+the \l{GNU General Public License, version 2}.
+See \l{Qt Licensing} for further details.
+
+On Linux, Qt Bluetooth uses a separate executable, \c sdpscanner,
+to integrate with the official Linux bluetooth protocol stack
+BlueZ. BlueZ is available under the \l{GNU General Public License,
+version 2}.
+
+\generatelist{groupsbymodule attributions-qtbluetooth}
*/
diff --git a/src/bluetooth/doc/src/bluetooth-le-overview.qdoc b/src/bluetooth/doc/src/bluetooth-le-overview.qdoc
index 8e78c487..a2fb10d9 100644
--- a/src/bluetooth/doc/src/bluetooth-le-overview.qdoc
+++ b/src/bluetooth/doc/src/bluetooth-le-overview.qdoc
@@ -37,8 +37,8 @@ Low Energy devices.
The Qt Bluetooth Low Energy API for the central role was introduced by Qt 5.4.
Since Qt 5.5 that part of the API is final and a compatibility guarantee is given for
future releases.
- In Qt 5.7, additional API supporting the peripheral role was added as a Technology Preview,
- with the backend only implemented for Linux/BlueZ.
+ Since Qt 5.7, additional API supporting the peripheral role was added as a Technology Preview,
+ with the backend implemented for Linux/BlueZ, iOS and macOS.
\section1 What Is Bluetooth Low Energy
diff --git a/src/bluetooth/osx/osxbt.pri b/src/bluetooth/osx/osxbt.pri
index 5ca833cc..13187e4f 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/osxbluetooth_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,22 @@ 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/osxbluetooth_p.h \
osx/osxbtcentralmanager_p.h \
osx/osxbtnotifier_p.h
+ ios {
+ PRIVATE_HEADERS += osx/osxbtperipheralmanager_p.h
+ }
OBJECTIVE_SOURCES += osx/osxbtutility.mm \
osx/osxbtledeviceinquiry.mm \
osx/osxbtcentralmanager.mm
+ ios {
+ OBJECTIVE_SOURCES += osx/osxbtperipheralmanager.mm
+ }
}
diff --git a/src/bluetooth/osx/osxbtcentralmanager.mm b/src/bluetooth/osx/osxbtcentralmanager.mm
index 06425753..f7218ca6 100644
--- a/src/bluetooth/osx/osxbtcentralmanager.mm
+++ b/src/bluetooth/osx/osxbtcentralmanager.mm
@@ -43,7 +43,6 @@
#include "osxbtnotifier_p.h"
#include <QtCore/qloggingcategory.h>
-#include <QtCore/qsysinfo.h>
#include <QtCore/qdebug.h>
#include <algorithm>
@@ -116,7 +115,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;
@@ -171,9 +170,9 @@ QT_END_NAMESPACE
if (!manager) {
managerState = OSXBluetooth::CentralManagerIdle;
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate a central manager";
+ qCWarning(QT_BT_OSX) << "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];
@@ -187,7 +186,7 @@ QT_END_NAMESPACE
Q_FUNC_INFO, "invalid state");
if ([self isConnected]) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "already connected";
+ qCDebug(QT_BT_OSX) << "already connected";
if (notifier)
emit notifier->connected();
return;
@@ -203,62 +202,37 @@ QT_END_NAMESPACE
// Retrieve a peripheral first ...
ObjCScopedPointer<NSMutableArray> uuids([[NSMutableArray alloc] init]);
if (!uuids) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate identifiers";
+ qCWarning(QT_BT_OSX) << "failed to allocate identifiers";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError);
+ emit notifier->CBManagerError(QLowEnergyController::ConnectionError);
return;
}
-#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_7_0)
- if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_7_0)) {
- const quint128 qtUuidData(deviceUuid.toUInt128());
- // STATIC_ASSERT on sizes would be handy!
- uuid_t uuidData = {};
- std::copy(qtUuidData.data, qtUuidData.data + 16, uuidData);
- const ObjCScopedPointer<NSUUID> nsUuid([[NSUUID alloc] initWithUUIDBytes:uuidData]);
- if (!nsUuid) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate NSUUID identifier";
- if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError);
- return;
- }
-
- [uuids addObject:nsUuid];
- // With the latest CoreBluetooth, we can synchronously retrive peripherals:
- QT_BT_MAC_AUTORELEASEPOOL;
- NSArray *const peripherals = [manager retrievePeripheralsWithIdentifiers:uuids];
- if (!peripherals || peripherals.count != 1) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to retrive a peripheral";
- if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError);
- return;
- }
- peripheral = [static_cast<CBPeripheral *>([peripherals objectAtIndex:0]) retain];
- [self connectToPeripheral];
- return;
- }
-#endif
- // Either SDK or the target is below 10.9/7.0
- if (![manager respondsToSelector:@selector(retrievePeripherals:)]) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to retrive a peripheral";
+ const quint128 qtUuidData(deviceUuid.toUInt128());
+ uuid_t uuidData = {};
+ std::copy(qtUuidData.data, qtUuidData.data + 16, uuidData);
+ const ObjCScopedPointer<NSUUID> nsUuid([[NSUUID alloc] initWithUUIDBytes:uuidData]);
+ if (!nsUuid) {
+ qCWarning(QT_BT_OSX) << "failed to allocate NSUUID identifier";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError);
+ emit notifier->CBManagerError(QLowEnergyController::ConnectionError);
return;
}
- OSXBluetooth::CFStrongReference<CFUUIDRef> cfUuid(OSXBluetooth::cf_uuid(deviceUuid));
- if (!cfUuid) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to create CFUUID object";
+ [uuids addObject:nsUuid];
+ // With the latest CoreBluetooth, we can synchronously retrive peripherals:
+ QT_BT_MAC_AUTORELEASEPOOL;
+ NSArray *const peripherals = [manager retrievePeripheralsWithIdentifiers:uuids];
+ if (!peripherals || peripherals.count != 1) {
+ qCWarning(QT_BT_OSX) << "failed to retrive a peripheral";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::ConnectionError);
+ emit notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
return;
}
- // With ARC this cast will be illegal:
- [uuids addObject:(id)cfUuid.data()];
- // Unfortunately, with old Core Bluetooth this call is asynchronous ...
- managerState = OSXBluetooth::CentralManagerConnecting;
- [manager performSelector:@selector(retrievePeripherals:) withObject:uuids.data()];
+
+ peripheral = [static_cast<CBPeripheral *>([peripherals objectAtIndex:0]) retain];
+ [self connectToPeripheral];
}
- (void)connectToPeripheral
@@ -270,11 +244,11 @@ QT_END_NAMESPACE
// The state is still the same - connecting.
if ([self isConnected]) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "already connected";
+ qCDebug(QT_BT_OSX) << "already connected";
if (notifier)
emit notifier->connected();
} else {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "trying to connect";
+ qCDebug(QT_BT_OSX) << "trying to connect";
managerState = OSXBluetooth::CentralManagerConnecting;
[manager connectPeripheral:peripheral options:nil];
}
@@ -285,18 +259,7 @@ QT_END_NAMESPACE
if (!peripheral)
return false;
-#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_7_0)
- using OSXBluetooth::qt_OS_limit;
-
- if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_7_0))
- return peripheral.state == CBPeripheralStateConnected;
-#endif
- // Either SDK or the target is below 10.9/7.0 ...
- if (![peripheral respondsToSelector:@selector(isConnected)])
- return false;
-
- // Ugly cast to deal with id being a pointer ...
- return reinterpret_cast<quintptr>([peripheral performSelector:@selector(isConnected)]);
+ return peripheral.state == CBPeripheralStateConnected;
}
- (void)disconnectFromDevice
@@ -389,7 +352,7 @@ QT_END_NAMESPACE
Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
if (servicesToDiscoverDetails.contains(serviceUuid)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO <<"already discovering for "
+ qCWarning(QT_BT_OSX) << "already discovering for"
<< serviceUuid;
return;
}
@@ -402,11 +365,11 @@ QT_END_NAMESPACE
return;
}
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown service uuid "
+ qCWarning(QT_BT_OSX) << "unknown service uuid"
<< serviceUuid;
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::UnknownError);
}
}
@@ -499,9 +462,9 @@ QT_END_NAMESPACE
const QLowEnergyHandle maxHandle = std::numeric_limits<QLowEnergyHandle>::max();
if (nHandles >= maxHandle || lastValidHandle > maxHandle - nHandles) {
// Well, that's unlikely :) But we must be sure.
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "can not allocate more handles";
+ qCWarning(QT_BT_OSX) << "can not allocate more handles";
if (notifier)
- notifier->CBCentralManagerError(serviceUuid, QLowEnergyService::OperationError);
+ notifier->CBManagerError(serviceUuid, QLowEnergyService::OperationError);
return;
}
@@ -603,7 +566,7 @@ QT_END_NAMESPACE
const LERequest request(requests.dequeue());
if (request.type == LERequest::CharRead) {
if (!charMap.contains(request.handle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "characteristic with handle"
+ qCWarning(QT_BT_OSX) << "characteristic with handle"
<< request.handle << "not found";
return [self performNextRequest];
}
@@ -613,7 +576,7 @@ QT_END_NAMESPACE
[peripheral readValueForCharacteristic:charMap[request.handle]];
} else {
if (!descMap.contains(request.handle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "descriptor with handle"
+ qCWarning(QT_BT_OSX) << "descriptor with handle"
<< request.handle << "not found";
return [self performNextRequest];
}
@@ -640,8 +603,8 @@ QT_END_NAMESPACE
if (request.type == LERequest::DescWrite) {
if (!descMap.contains(request.handle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "handle: "
- << request.handle << " not found";
+ qCWarning(QT_BT_OSX) << "handle:" << request.handle
+ << "not found";
return [self performNextRequest];
}
@@ -649,8 +612,7 @@ QT_END_NAMESPACE
ObjCStrongReference<NSData> data(data_from_bytearray(request.value));
if (!data) {
// Even if qtData.size() == 0, we still need NSData object.
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed "
- "to allocate an NSData object";
+ qCWarning(QT_BT_OSX) << "failed to allocate an NSData object";
return [self performNextRequest];
}
@@ -661,8 +623,8 @@ QT_END_NAMESPACE
return [peripheral writeValue:data.data() forDescriptor:descriptor];
} else {
if (!charMap.contains(request.handle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "characteristic with "
- "handle: " << request.handle << " not found";
+ qCWarning(QT_BT_OSX) << "characteristic with handle:"
+ << request.handle << "not found";
return [self performNextRequest];
}
@@ -687,7 +649,7 @@ QT_END_NAMESPACE
ObjCStrongReference<NSData> data(data_from_bytearray(request.value));
if (!data) {
// Even if qtData.size() == 0, we still need NSData object.
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate NSData object";
+ qCWarning(QT_BT_OSX) << "failed to allocate NSData object";
return [self performNextRequest];
}
@@ -717,11 +679,10 @@ QT_END_NAMESPACE
Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle (0)");
if (!charMap.contains(charHandle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "unknown characteristic handle "
+ qCWarning(QT_BT_OSX) << "unknown characteristic handle"
<< charHandle;
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::DescriptorWriteError);
}
return;
@@ -732,10 +693,9 @@ QT_END_NAMESPACE
// it back, so check _now_ that we really have this descriptor.
const QBluetoothUuid qtUuid(QBluetoothUuid::ClientCharacteristicConfiguration);
if (![self descriptor:qtUuid forCharacteristic:charMap[charHandle]]) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "no client characteristic configuration found";
+ qCWarning(QT_BT_OSX) << "no client characteristic configuration found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::DescriptorWriteError);
}
return;
@@ -760,9 +720,9 @@ QT_END_NAMESPACE
QT_BT_MAC_AUTORELEASEPOOL;
if (!charMap.contains(charHandle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "characteristic: " << charHandle << " not found";
+ qCWarning(QT_BT_OSX) << "characteristic:" << charHandle << "not found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::CharacteristicReadError);
}
@@ -789,10 +749,9 @@ QT_END_NAMESPACE
QT_BT_MAC_AUTORELEASEPOOL;
if (!charMap.contains(charHandle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "characteristic: "
- << charHandle << " not found";
+ qCWarning(QT_BT_OSX) << "characteristic:" << charHandle << "not found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::CharacteristicWriteError);
}
return;
@@ -816,10 +775,9 @@ QT_END_NAMESPACE
Q_ASSERT_X(descHandle, Q_FUNC_INFO, "invalid descriptor handle (0)");
if (!descMap.contains(descHandle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "handle:"
- << descHandle << "not found";
+ qCWarning(QT_BT_OSX) << "handle:" << descHandle << "not found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::DescriptorReadError);
}
return;
@@ -842,10 +800,9 @@ QT_END_NAMESPACE
Q_ASSERT_X(descHandle, Q_FUNC_INFO, "invalid descriptor handle (0)");
if (!descMap.contains(descHandle)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "handle: "
- << descHandle << " not found";
+ qCWarning(QT_BT_OSX) << "handle:" << descHandle << "not found";
if (notifier) {
- emit notifier->CBCentralManagerError(serviceUuid,
+ emit notifier->CBManagerError(serviceUuid,
QLowEnergyService::DescriptorWriteError);
}
return;
@@ -1029,27 +986,25 @@ QT_END_NAMESPACE
if ([obj isKindOfClass:[CBCharacteristic class]]) {
CBCharacteristic *const ch = static_cast<CBCharacteristic *>(obj);
if (!charMap.key(ch)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected "
- "characteristic, no handle found";
+ qCWarning(QT_BT_OSX) << "unexpected characteristic, no handle found";
return false;
}
} else if ([obj isKindOfClass:[CBDescriptor class]]) {
CBDescriptor *const d = static_cast<CBDescriptor *>(obj);
if (!descMap.key(d)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected "
- "descriptor, no handle found";
+ qCWarning(QT_BT_OSX) << "unexpected descriptor, no handle found";
return false;
}
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid object, "
- "characteristic or descriptor required";
+ qCWarning(QT_BT_OSX) << "invalid object, characteristic "
+ "or descriptor required";
return false;
}
if (valuesToWrite.contains(obj)) {
// It can be a result of some previous errors - for example,
// we never got a callback from a previous write.
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "already has a cached value for this "
+ qCWarning(QT_BT_OSX) << "already has a cached value for this "
"object, the value will be replaced";
}
@@ -1114,7 +1069,7 @@ QT_END_NAMESPACE
// and reset managerState from CentralManagerUpdating.
managerState = CentralManagerIdle;
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::InvalidBluetoothAdapterError);
+ emit notifier->CBManagerError(QLowEnergyController::InvalidBluetoothAdapterError);
}
return;
}
@@ -1133,7 +1088,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;
}
@@ -1153,30 +1108,6 @@ QT_END_NAMESPACE
}
}
-- (void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals
-{
- Q_UNUSED(central)
-
- // This method is required for iOS before 7.0 and OS X below 10.9.
- Q_ASSERT_X(manager, Q_FUNC_INFO, "invalid central manager (nil)");
-
- if (managerState != OSXBluetooth::CentralManagerConnecting) {
- // Canceled by calling -disconnectFromDevice method.
- return;
- }
-
- managerState = OSXBluetooth::CentralManagerIdle;
-
- if (!peripherals || peripherals.count != 1) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO <<"unexpected number of peripherals (!= 1)";
- if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError);
- } else {
- peripheral = [static_cast<CBPeripheral *>([peripherals objectAtIndex:0]) retain];
- [self connectToPeripheral];
- }
-}
-
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)aPeripheral
{
Q_UNUSED(central)
@@ -1207,7 +1138,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
@@ -1221,9 +1152,9 @@ QT_END_NAMESPACE
if (error && managerState == OSXBluetooth::CentralManagerDisconnecting) {
managerState = OSXBluetooth::CentralManagerIdle;
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to disconnect";
+ qCWarning(QT_BT_OSX) << "failed to disconnect";
if (notifier)
- emit notifier->CBCentralManagerError(QLowEnergyController::UnknownRemoteDeviceError);
+ emit notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
} else {
managerState = OSXBluetooth::CentralManagerIdle;
if (notifier)
@@ -1248,7 +1179,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];
}
@@ -1344,7 +1275,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];
}
@@ -1379,7 +1310,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;
@@ -1402,7 +1333,7 @@ QT_END_NAMESPACE
// updated values ...
// TODO: this must be properly tested.
if (!chHandle) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "unexpected update notification, "
+ qCCritical(QT_BT_OSX) << "unexpected update notification, "
"no characteristic handle found";
return;
}
@@ -1481,7 +1412,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;
@@ -1513,7 +1444,7 @@ QT_END_NAMESPACE
}
} else {
if (!dHandle) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "unexpected value update notification, "
+ qCCritical(QT_BT_OSX) << "unexpected value update notification, "
"no descriptor handle found";
return;
}
@@ -1556,13 +1487,13 @@ QT_END_NAMESPACE
// Error or not, but the cached value has to be deleted ...
const QByteArray valueToReport(valuesToWrite.value(characteristic, QByteArray()));
if (!valuesToWrite.remove(characteristic)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no updated value found"
- " for characteristic";
+ qCWarning(QT_BT_OSX) << "no updated value found "
+ "for characteristic";
}
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);
@@ -1592,16 +1523,15 @@ QT_END_NAMESPACE
// Error or not, a value (if any) must be removed.
const QByteArray valueToReport(valuesToWrite.value(descriptor, QByteArray()));
if (!valuesToWrite.remove(descriptor))
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no updated value found";
+ qCWarning(QT_BT_OSX) << "no updated value found";
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);
- Q_ASSERT_X(dHandle, Q_FUNC_INFO,
- "descriptor not found in the descriptors map");
+ Q_ASSERT_X(dHandle, Q_FUNC_INFO, "descriptor not found in the descriptors map");
emit notifier->descriptorWritten(dHandle, valueToReport);
}
@@ -1631,7 +1561,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 3eee2190..697d922c 100644
--- a/src/bluetooth/osx/osxbtcentralmanager_p.h
+++ b/src/bluetooth/osx/osxbtcentralmanager_p.h
@@ -72,7 +72,7 @@ class QLowEnergyServicePrivate;
namespace OSXBluetooth {
-class LECentralNotifier;
+class LECBManagerNotifier;
enum CentralManagerState
{
@@ -141,7 +141,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").
@@ -170,7 +170,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/osxbtdeviceinquiry.mm b/src/bluetooth/osx/osxbtdeviceinquiry.mm
index 0420a67e..79a8a92c 100644
--- a/src/bluetooth/osx/osxbtdeviceinquiry.mm
+++ b/src/bluetooth/osx/osxbtdeviceinquiry.mm
@@ -73,8 +73,7 @@ QT_USE_NAMESPACE
[m_inquiry setUpdateNewDeviceNames:NO];//Useless, disable!
m_delegate = delegate;
} else {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create "
- "a device inquiry";
+ qCCritical(QT_BT_OSX) << "failed to create a device inquiry";
}
m_active = false;
@@ -113,8 +112,7 @@ QT_USE_NAMESPACE
if (result != kIOReturnSuccess) {
// QtBluetooth will probably convert an error into UnknownError,
// loosing the actual information.
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO <<"failed with "
- "IOKit error code: " << result;
+ qCWarning(QT_BT_OSX) << "failed with IOKit error code:" << result;
m_active = false;
}
@@ -130,6 +128,8 @@ QT_USE_NAMESPACE
const IOReturn res = [m_inquiry stop];
if (res != kIOReturnSuccess)
m_active = true;
+ else
+ qCDebug(QT_BT_OSX) << "-stop, success (waiting for 'inquiryComplete')";
return res;
}
@@ -152,7 +152,7 @@ QT_USE_NAMESPACE
if (error != kIOReturnSuccess) {
// QtBluetooth has not too many error codes, 'UnknownError' is not really
// useful, report the actual error code here:
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "IOKit error code: " << error;
+ qCWarning(QT_BT_OSX) << "IOKit error code: " << error;
m_delegate->error(sender, error);
} else {
m_delegate->inquiryFinished(sender);
diff --git a/src/bluetooth/osx/osxbtdevicepair.mm b/src/bluetooth/osx/osxbtdevicepair.mm
index dbb2fa4b..dcaa3536 100644
--- a/src/bluetooth/osx/osxbtdevicepair.mm
+++ b/src/bluetooth/osx/osxbtdevicepair.mm
@@ -41,7 +41,6 @@
#include "osxbtutility_p.h"
#include <QtCore/qloggingcategory.h>
-#include <QtCore/qsysinfo.h>
#include <QtCore/qdebug.h>
QT_BEGIN_NAMESPACE
@@ -88,18 +87,8 @@ QT_USE_NAMESPACE
- (void)dealloc
{
-#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9)
- // Stop also sets a delegate to nil (Apple's docs).
- // 10.9 only.
- if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9)
- [m_pairing stop];
- else
- [m_pairing setDelegate:nil];
-#else
- [m_pairing setDelegate:nil];
-#endif
+ [m_pairing stop];
[m_pairing release];
-
[super dealloc];
}
@@ -116,14 +105,13 @@ QT_USE_NAMESPACE
// Device is autoreleased.
IOBluetoothDevice *const device = [IOBluetoothDevice deviceWithAddress:&iobtAddress];
if (!device) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create a device "
- "to pair with";
+ qCCritical(QT_BT_OSX) << "failed to create a device to pair with";
return kIOReturnError;
}
m_pairing = [[IOBluetoothDevicePair pairWithDevice:device] retain];
if (!m_pairing) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create pair";
+ qCCritical(QT_BT_OSX) << "failed to create pair";
return kIOReturnError;
}
diff --git a/src/bluetooth/osx/osxbtl2capchannel.mm b/src/bluetooth/osx/osxbtl2capchannel.mm
index e18e9e25..02ec4f90 100644
--- a/src/bluetooth/osx/osxbtl2capchannel.mm
+++ b/src/bluetooth/osx/osxbtl2capchannel.mm
@@ -104,13 +104,13 @@ QT_USE_NAMESPACE
withPSM:(BluetoothL2CAPChannelID)psm
{
if (address.isNull()) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "invalid peer address";
+ qCCritical(QT_BT_OSX) << "invalid peer address";
return kIOReturnNoDevice;
}
// Can never be called twice.
if (connected || device || channel) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "connection is already active";
+ qCCritical(QT_BT_OSX) << "connection is already active";
return kIOReturnStillOpen;
}
@@ -119,13 +119,13 @@ QT_USE_NAMESPACE
const BluetoothDeviceAddress iobtAddress = OSXBluetooth::iobluetooth_address(address);
device = [IOBluetoothDevice deviceWithAddress:&iobtAddress];
if (!device) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create a device";
+ qCCritical(QT_BT_OSX) << "failed to create a device";
return kIOReturnNoDevice;
}
const IOReturn status = [device openL2CAPChannelAsync:&channel withPSM:psm delegate:self];
if (status != kIOReturnSuccess) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to open L2CAP channel";
+ qCCritical(QT_BT_OSX) << "failed to open L2CAP channel";
// device is still autoreleased.
device = nil;
return status;
diff --git a/src/bluetooth/osx/osxbtledeviceinquiry.mm b/src/bluetooth/osx/osxbtledeviceinquiry.mm
index 55ffc36f..2dabd0c1 100644
--- a/src/bluetooth/osx/osxbtledeviceinquiry.mm
+++ b/src/bluetooth/osx/osxbtledeviceinquiry.mm
@@ -39,19 +39,19 @@
#include "osxbtledeviceinquiry_p.h"
#include "qbluetoothdeviceinfo.h"
+#include "osxbtnotifier_p.h"
#include "qbluetoothuuid.h"
#include "osxbtutility_p.h"
#include <QtCore/qloggingcategory.h>
-#include <QtCore/qsysinfo.h>
#include <QtCore/qdebug.h>
+#include <algorithm>
+
QT_BEGIN_NAMESPACE
namespace OSXBluetooth {
-#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_6_0)
-
QBluetoothUuid qt_uuid(NSUUID *nsUuid)
{
if (!nsUuid)
@@ -64,58 +64,74 @@ QBluetoothUuid qt_uuid(NSUUID *nsUuid)
return QBluetoothUuid(qtUuidData);
}
-#endif
-
-QBluetoothUuid qt_uuid(CFUUIDRef uuid)
+const int timeStepMS = 100;
+
+const int powerOffTimeoutMS = 30000;
+const qreal powerOffTimeStepS = 30. / 100.;
+
+struct AdvertisementData {
+ // That's what CoreBluetooth has:
+ // CBAdvertisementDataLocalNameKey
+ // CBAdvertisementDataTxPowerLevelKey
+ // CBAdvertisementDataServiceUUIDsKey
+ // CBAdvertisementDataServiceDataKey
+ // CBAdvertisementDataManufacturerDataKey
+ // CBAdvertisementDataOverflowServiceUUIDsKey
+ // CBAdvertisementDataIsConnectable
+ // CBAdvertisementDataSolicitedServiceUUIDsKey
+
+ // For now, we "parse":
+ QString localName;
+ QList<QBluetoothUuid> serviceUuids;
+ // TODO: other keys probably?
+ AdvertisementData(NSDictionary *AdvertisementData);
+};
+
+AdvertisementData::AdvertisementData(NSDictionary *advertisementData)
{
- if (!uuid)
- return QBluetoothUuid();
-
- const CFUUIDBytes data = CFUUIDGetUUIDBytes(uuid);
- quint128 qtUuidData = {{data.byte0, data.byte1, data.byte2, data.byte3,
- data.byte4, data.byte5, data.byte6, data.byte7,
- data.byte8, data.byte9, data.byte10, data.byte11,
- data.byte12, data.byte13, data.byte14, data.byte15}};
-
- return QBluetoothUuid(qtUuidData);
-}
-
-typedef ObjCStrongReference<NSString> StringStrongReference;
+ if (!advertisementData)
+ return;
-StringStrongReference uuid_as_nsstring(CFUUIDRef uuid)
-{
- // We use the UUDI's string representation as a key in a dictionary.
- if (!uuid)
- return StringStrongReference();
+ // ... constant CBAdvertisementDataLocalNameKey ...
+ // NSString containing the local name of a peripheral.
+ NSObject *value = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
+ if (value && [value isKindOfClass:[NSString class]])
+ localName = QString::fromNSString(static_cast<NSString *>(value));
- CFStringRef cfStr = CFUUIDCreateString(kCFAllocatorDefault, uuid);
- if (!cfStr)
- return StringStrongReference();
+ // ... constant CBAdvertisementDataServiceUUIDsKey ...
+ // A list of one or more CBUUID objects, representing CBService UUIDs.
- // Imporant: with ARC this will require a different cast/ownership!
- return StringStrongReference((NSString *)cfStr, false);
+ value = [advertisementData objectForKey:CBAdvertisementDataServiceUUIDsKey];
+ if (value && [value isKindOfClass:[NSArray class]]) {
+ NSArray *uuids = static_cast<NSArray *>(value);
+ for (CBUUID *cbUuid in uuids)
+ serviceUuids << qt_uuid(cbUuid);
+ }
}
}
-
QT_END_NAMESPACE
QT_USE_NAMESPACE
@interface QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) (PrivateAPI) <CBCentralManagerDelegate>
+// These two methods are scheduled with a small time step
+// within a given timeout, they either re-schedule
+// themselves or emit a signal/stop some operation.
- (void)stopScan;
- (void)handlePoweredOff;
@end
@implementation QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry)
-- (id)init
+-(id)initWithNotifier:(LECBManagerNotifier *)aNotifier
{
if (self = [super init]) {
- uuids.reset([[NSMutableSet alloc] init]);
+ Q_ASSERT(aNotifier);
+ notifier = aNotifier;
internalState = InquiryStarting;
- state.store(int(internalState));
+ inquiryTimeoutMS = OSXBluetooth::defaultLEScanTimeoutMS;
}
return self;
@@ -129,27 +145,36 @@ QT_USE_NAMESPACE
[manager stopScan];
}
+ if (notifier) {
+ notifier->disconnect();
+ notifier->deleteLater();
+ }
+
[super dealloc];
}
- (void)stopScan
{
- // Scan's "timeout" - we consider LE device
- // discovery finished.
using namespace OSXBluetooth;
+ // We never schedule stopScan if there is no timeout:
+ Q_ASSERT(inquiryTimeoutMS > 0);
+
if (internalState == InquiryActive) {
- if (scanTimer.elapsed() >= qt_LE_deviceInquiryLength() * 1000) {
- // We indeed stop now:
+ const int elapsed = scanTimer.elapsed();
+ if (elapsed >= inquiryTimeoutMS) {
[manager stopScan];
[manager setDelegate:nil];
internalState = InquiryFinished;
- state.store(int(internalState));
+ Q_ASSERT(notifier);
+ emit notifier->discoveryFinished();
} else {
+ // Re-schedule 'stopScan':
dispatch_queue_t leQueue(qt_LE_queue());
Q_ASSERT(leQueue);
+ const int timeChunkMS = std::min(inquiryTimeoutMS - elapsed, timeStepMS);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
- int64_t(qt_LE_deviceInquiryLength() / 100. * NSEC_PER_SEC)),
+ int64_t(timeChunkMS / 1000. * NSEC_PER_SEC)),
leQueue,
^{
[self stopScan];
@@ -164,30 +189,32 @@ QT_USE_NAMESPACE
// the system shows an alert asking to enable
// Bluetooth in the 'Settings' app. If not done yet (after 30
// seconds) - we consider it an error.
+ using namespace OSXBluetooth;
+
if (internalState == InquiryStarting) {
- if (errorTimer.elapsed() >= 30000) {
+ if (errorTimer.elapsed() >= powerOffTimeoutMS) {
[manager setDelegate:nil];
internalState = ErrorPoweredOff;
- state.store(int(internalState));
+ Q_ASSERT(notifier);
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
} else {
- dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
+ dispatch_queue_t leQueue(qt_LE_queue());
Q_ASSERT(leQueue);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
- (int64_t)(30 / 100. * NSEC_PER_SEC)),
+ (int64_t)(powerOffTimeStepS * NSEC_PER_SEC)),
leQueue,
^{
[self handlePoweredOff];
});
-
}
}
}
-- (void)start
+- (void)startWithTimeout:(int)timeout
{
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
-
Q_ASSERT(leQueue);
+ inquiryTimeoutMS = timeout;
manager.reset([[CBCentralManager alloc] initWithDelegate:self queue:leQueue]);
}
@@ -199,61 +226,73 @@ QT_USE_NAMESPACE
if (internalState != InquiryActive && internalState != InquiryStarting)
return;
+ Q_ASSERT(notifier);
+
using namespace OSXBluetooth;
dispatch_queue_t leQueue(qt_LE_queue());
Q_ASSERT(leQueue);
#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0)
- const CBManagerState cbState(central.state);
- if (cbState == CBManagerStatePoweredOn) {
+ const CBManagerState state(central.state);
+ if (state == CBManagerStatePoweredOn) {
#else
- const CBCentralManagerState cbState(central.state);
- if (cbState == CBCentralManagerStatePoweredOn) {
+ const CBCentralManagerState state(central.state);
+ if (state == CBCentralManagerStatePoweredOn) {
#endif
if (internalState == InquiryStarting) {
internalState = InquiryActive;
- // Scan time is actually 10 seconds. Having a block with such delay can prevent
- // 'self' from being deleted in time, which is not good. So we split this
- // 10 s. timeout into smaller 'chunks'.
- scanTimer.start();
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
- int64_t(qt_LE_deviceInquiryLength() / 100. * NSEC_PER_SEC)),
- leQueue,
- ^{
- [self stopScan];
- });
+
+ if (inquiryTimeoutMS > 0) {
+ // We have a finite-length discovery, schedule stopScan,
+ // with a smaller time step, otherwise it can prevent
+ // 'self' from being deleted in time, which is not good
+ // (the block will retain 'self', waiting for timeout).
+ scanTimer.start();
+ const int timeChunkMS = std::min(timeStepMS, inquiryTimeoutMS);
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
+ int64_t(timeChunkMS / 1000. * NSEC_PER_SEC)),
+ leQueue,
+ ^{
+ [self stopScan];
+ });
+ }
+
[manager scanForPeripheralsWithServices:nil options:nil];
} // Else we ignore.
#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0)
- } else if (cbState == CBManagerStateUnsupported || cbState == CBManagerStateUnauthorized) {
+ } else if (state == CBManagerStateUnsupported || state == CBManagerStateUnauthorized) {
#else
- } else if (cbState == CBCentralManagerStateUnsupported || cbState == CBCentralManagerStateUnauthorized) {
+ } else if (state == CBCentralManagerStateUnsupported || state == CBCentralManagerStateUnauthorized) {
#endif
if (internalState == InquiryActive) {
[manager stopScan];
- // Not sure how this is possible at all, probably, can never happen.
+ // Not sure how this is possible at all,
+ // probably, can never happen.
internalState = ErrorPoweredOff;
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
} else {
internalState = ErrorLENotSupported;
+ emit notifier->LEnotSupported();
}
[manager setDelegate:nil];
#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0)
- } else if (cbState == CBManagerStatePoweredOff) {
+ } else if (state == CBManagerStatePoweredOff) {
#else
- } else if (cbState == CBCentralManagerStatePoweredOff) {
+ } else if (state == CBCentralManagerStatePoweredOff) {
#endif
if (internalState == InquiryStarting) {
#ifndef Q_OS_OSX
- // On iOS a user can see at this point an alert asking to enable
- // Bluetooth in the "Settings" app. If a user does,
+ // On iOS a user can see at this point an alert asking to
+ // enable Bluetooth in the "Settings" app. If a user does,
// we'll receive 'PoweredOn' state update later.
- // No change in state. Wait for 30 seconds (we split it into 'chunks' not
- // to retain 'self' for too long ) ...
+ // No change in internalState. Wait for 30 seconds
+ // (we split it into smaller steps not to retain 'self' for
+ // too long ) ...
errorTimer.start();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
- (int64_t)(30 / 100. * NSEC_PER_SEC)),
+ (int64_t)(powerOffTimeStepS * NSEC_PER_SEC)),
leQueue,
^{
[self handlePoweredOff];
@@ -261,9 +300,10 @@ QT_USE_NAMESPACE
return;
#endif
internalState = ErrorPoweredOff;
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
} else {
- internalState = ErrorPoweredOff;
[manager stopScan];
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
}
[manager setDelegate:nil];
@@ -279,8 +319,6 @@ QT_USE_NAMESPACE
// lost; an update is imminent. "
// Wait for this imminent update.
}
-
- state.store(int(internalState));
}
- (void)stop
@@ -290,14 +328,17 @@ QT_USE_NAMESPACE
[manager setDelegate:nil];
internalState = InquiryCancelled;
- state.store(int(internalState));
+
+ notifier->disconnect();
+ notifier->deleteLater();
+ notifier = nullptr;
}
-- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral
- advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
+- (void)centralManager:(CBCentralManager *)central
+ didDiscoverPeripheral:(CBPeripheral *)peripheral
+ advertisementData:(NSDictionary *)advertisementData
+ RSSI:(NSNumber *)RSSI
{
- Q_UNUSED(advertisementData);
-
using namespace OSXBluetooth;
if (central != manager)
@@ -306,49 +347,20 @@ QT_USE_NAMESPACE
if (internalState != InquiryActive)
return;
- QBluetoothUuid deviceUuid;
-
-#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_7_0)
- if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_7_0)) {
- if (!peripheral.identifier) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "peripheral without NSUUID";
- return;
- }
+ if (!notifier)
+ return;
- if ([uuids containsObject:peripheral.identifier]) {
- // We already know this peripheral ...
- return;
- }
+ QBluetoothUuid deviceUuid;
- [uuids addObject:peripheral.identifier];
- deviceUuid = OSXBluetooth::qt_uuid(peripheral.identifier);
+ if (!peripheral.identifier) {
+ qCWarning(QT_BT_OSX) << "peripheral without NSUUID";
+ return;
}
-#endif
- // Either SDK or the target is below 10.9/7.0:
- // The property UUID was finally removed in iOS 9, we have
- // to avoid compilation errors ...
- if (deviceUuid.isNull()) {
- CFUUIDRef cfUUID = Q_NULLPTR;
-
- if ([peripheral respondsToSelector:@selector(UUID)]) {
- // This will require a bridged cast if we switch to ARC ...
- cfUUID = reinterpret_cast<CFUUIDRef>([peripheral performSelector:@selector(UUID)]);
- }
-
- if (!cfUUID) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "peripheral without CFUUID";
- return;
- }
- StringStrongReference key(uuid_as_nsstring(cfUUID));
- if ([uuids containsObject:key.data()])
- return; // We've seen this peripheral before ...
- [uuids addObject:key.data()];
- deviceUuid = OSXBluetooth::qt_uuid(cfUUID);
- }
+ deviceUuid = OSXBluetooth::qt_uuid(peripheral.identifier);
if (deviceUuid.isNull()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no way to address peripheral, QBluetoothUuid is null";
+ qCWarning(QT_BT_OSX) << "no way to address peripheral, QBluetoothUuid is null";
return;
}
@@ -356,23 +368,23 @@ QT_USE_NAMESPACE
if (peripheral.name)
name = QString::fromNSString(peripheral.name);
+ const AdvertisementData qtAdvData(advertisementData);
+ if (!name.size()) // Probably, it's not possible to have one and not the other.
+ name = qtAdvData.localName;
+
// TODO: fix 'classOfDevice' (0 for now).
QBluetoothDeviceInfo newDeviceInfo(deviceUuid, name, 0);
if (RSSI)
newDeviceInfo.setRssi([RSSI shortValue]);
- // CoreBluetooth scans only for LE devices.
- newDeviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
- devices.append(newDeviceInfo);
-}
-- (LEInquiryState) inquiryState
-{
- return LEInquiryState(state.load());
-}
+ if (qtAdvData.serviceUuids.size()) {
+ newDeviceInfo.setServiceUuids(qtAdvData.serviceUuids,
+ QBluetoothDeviceInfo::DataIncomplete);
+ }
-- (const QList<QBluetoothDeviceInfo> &)discoveredDevices
-{
- return devices;
+ // CoreBluetooth scans only for LE devices.
+ newDeviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
+ emit notifier->deviceDiscovered(newDeviceInfo);
}
@end
diff --git a/src/bluetooth/osx/osxbtledeviceinquiry_p.h b/src/bluetooth/osx/osxbtledeviceinquiry_p.h
index 71e8ef53..fc787a6d 100644
--- a/src/bluetooth/osx/osxbtledeviceinquiry_p.h
+++ b/src/bluetooth/osx/osxbtledeviceinquiry_p.h
@@ -58,7 +58,6 @@
#include <QtCore/qelapsedtimer.h>
#include <QtCore/qglobal.h>
-#include <QtCore/qatomic.h>
#include <QtCore/qlist.h>
#include <Foundation/Foundation.h>
@@ -67,8 +66,20 @@ QT_BEGIN_NAMESPACE
class QBluetoothUuid;
+namespace OSXBluetooth
+{
+
+class LECBManagerNotifier;
+
+}
+
QT_END_NAMESPACE
+// Ugly but all these QT_PREPEND_NAMESPACE etc. are even worse ...
+using OSXBluetooth::LECBManagerNotifier;
+using OSXBluetooth::ObjCScopedPointer;
+using QT_PREPEND_NAMESPACE(QElapsedTimer);
+
enum LEInquiryState
{
InquiryStarting,
@@ -81,29 +92,26 @@ enum LEInquiryState
@interface QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) : NSObject
{
- QT_PREPEND_NAMESPACE(OSXBluetooth)::ObjCScopedPointer<NSMutableSet> uuids;
- QT_PREPEND_NAMESPACE(OSXBluetooth)::ObjCScopedPointer<CBCentralManager> manager;
+ LECBManagerNotifier *notifier;
+ ObjCScopedPointer<CBCentralManager> manager;
QList<QBluetoothDeviceInfo> devices;
-
LEInquiryState internalState;
- QT_PREPEND_NAMESPACE(QAtomicInt) state;
+ int inquiryTimeoutMS;
// Timers to check if we can execute delayed callbacks:
QT_PREPEND_NAMESPACE(QElapsedTimer) errorTimer;
QT_PREPEND_NAMESPACE(QElapsedTimer) scanTimer;
}
-- (id)init;
+- (id)initWithNotifier:(LECBManagerNotifier *)aNotifier;
- (void)dealloc;
-// IMPORTANT: both 'start' and 'stop' are to be executed on the "Qt's LE queue".
-- (void)start;
+// IMPORTANT: both 'startWithTimeout' and 'stop'
+// can be executed only on the "Qt's LE queue".
+- (void)startWithTimeout:(int)timeout;
- (void)stop;
-- (LEInquiryState)inquiryState;
-- (const QList<QBluetoothDeviceInfo> &)discoveredDevices;
-
@end
#endif
diff --git a/src/bluetooth/osx/osxbtnotifier_p.h b/src/bluetooth/osx/osxbtnotifier_p.h
index 6cb2b019..47ee6ba1 100644
--- a/src/bluetooth/osx/osxbtnotifier_p.h
+++ b/src/bluetooth/osx/osxbtnotifier_p.h
@@ -52,7 +52,9 @@
//
+#include "qbluetoothdevicediscoveryagent.h"
#include "qlowenergycontroller.h"
+#include "qbluetoothdeviceinfo.h"
#include "qbluetoothuuid.h"
#include "qbluetooth.h"
@@ -68,11 +70,14 @@ class QLowEnergyServicePrivate;
namespace OSXBluetooth
{
-class LECentralNotifier : public QObject
+class LECBManagerNotifier : public QObject
{
Q_OBJECT
Q_SIGNALS:
+ void deviceDiscovered(QBluetoothDeviceInfo deviceInfo);
+ void discoveryFinished();
+
void connected();
void disconnected();
@@ -83,11 +88,13 @@ Q_SIGNALS:
void characteristicUpdated(QLowEnergyHandle charHandle, const QByteArray &value);
void descriptorRead(QLowEnergyHandle descHandle, const QByteArray &value);
void descriptorWritten(QLowEnergyHandle descHandle, const QByteArray &value);
+ void notificationEnabled(QLowEnergyHandle charHandle, bool enabled);
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(QBluetoothDeviceDiscoveryAgent::Error 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/osxbtobexsession.mm b/src/bluetooth/osx/osxbtobexsession.mm
index 9e324405..b8e604c8 100644
--- a/src/bluetooth/osx/osxbtobexsession.mm
+++ b/src/bluetooth/osx/osxbtobexsession.mm
@@ -203,7 +203,7 @@ QList<OBEXHeader> qt_bluetooth_headers(const uint8_t *data, std::size_t length)
break;
}
default:
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid header format";
+ qCWarning(QT_BT_OSX) << "invalid header format";
return empty;
}
@@ -358,7 +358,7 @@ bool check_connect_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode
response = e->u.connectCommandResponseData.serverResponseOpCode;
return response == kOBEXResponseCodeSuccessWithFinalBit;
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected event type";
+ qCWarning(QT_BT_OSX) << "unexpected event type";
error = kOBEXGeneralError;
return false;
}
@@ -378,7 +378,7 @@ bool check_put_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode &re
return response == kOBEXResponseCodeContinueWithFinalBit ||
response == kOBEXResponseCodeSuccessWithFinalBit;
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected event type";
+ qCWarning(QT_BT_OSX) << "unexpected event type";
error = kOBEXGeneralError;
return false;
}
@@ -395,7 +395,7 @@ bool check_abort_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode &
response = e->u.abortCommandResponseData.serverResponseOpCode;
return response == kOBEXResponseCodeSuccessWithFinalBit;
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected event type";
+ qCWarning(QT_BT_OSX) << "unexpected event type";
return false;
}
}
@@ -443,13 +443,13 @@ QT_USE_NAMESPACE
const BluetoothDeviceAddress addr(OSXBluetooth::iobluetooth_address(deviceAddress));
device = [[IOBluetoothDevice deviceWithAddress:&addr] retain];
if (!device) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to create an IOBluetoothDevice";
+ qCWarning(QT_BT_OSX) << "failed to create an IOBluetoothDevice";
return self;
}
session = [[IOBluetoothOBEXSession alloc] initWithDevice:device channelID:port];
if (!session) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to create an OBEX session";
+ qCWarning(QT_BT_OSX) << "failed to create an OBEX session";
return self;
}
@@ -474,7 +474,7 @@ QT_USE_NAMESPACE
- (OBEXError)OBEXConnect
{
if (!session) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid session (nil)";
+ qCWarning(QT_BT_OSX) << "invalid session (nil)";
return kOBEXGeneralError;
}
@@ -517,7 +517,7 @@ QT_USE_NAMESPACE
}
if (currentRequest != OBEXConnect) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called while there is no "
+ qCWarning(QT_BT_OSX) << "called while there is no "
"active connect request";
return;
}
@@ -596,7 +596,7 @@ QT_USE_NAMESPACE
Q_ASSERT_X(session, Q_FUNC_INFO, "invalid OBEX session (nil)");
if (currentRequest != OBEXAbort) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called while there "
+ qCWarning(QT_BT_OSX) << "called while there "
"is no ABORT request";
return;
}
@@ -628,13 +628,13 @@ QT_USE_NAMESPACE
// a payload.
const qint64 fileSize = input->size();
if (fileSize <= 0 || fileSize >= std::numeric_limits<uint32_t>::max()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid input file size";
+ qCWarning(QT_BT_OSX) << "invalid input file size";
return kOBEXBadArgumentError;
}
ObjCStrongReference<NSMutableData> headers([[NSMutableData alloc] init], false);
if (!headers) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate headers";
+ qCWarning(QT_BT_OSX) << "failed to allocate headers";
return kOBEXNoResourcesError;
}
@@ -644,16 +644,14 @@ QT_USE_NAMESPACE
if (connectionIDFound) {
if (!append_four_byte_header(headers, kOBEXHeaderIDConnectionID, connectionID)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "append connection ID header";
+ qCWarning(QT_BT_OSX) << "failed to append connection ID header";
return kOBEXNoResourcesError;
}
}
if (name.length()) {
if (!append_unicode_header(headers, kOBEXHeaderIDName, name)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to append "
- "a unicode string";
+ qCWarning(QT_BT_OSX) << "failed to append a unicode string";
return kOBEXNoResourcesError;
}
}
@@ -666,7 +664,7 @@ QT_USE_NAMESPACE
if (!chunk || ![chunk length]) {
// We do not support PUT-DELETE (?)
// At least the first chunk is expected to be non-empty.
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid input stream";
+ qCWarning(QT_BT_OSX) << "invalid input stream";
return kOBEXBadArgumentError;
}
@@ -713,7 +711,7 @@ QT_USE_NAMESPACE
}
if (currentRequest != OBEXPut) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called while the current "
+ qCWarning(QT_BT_OSX) << "called while the current "
"request is not a put request";
return;
}
@@ -735,8 +733,7 @@ QT_USE_NAMESPACE
// 0 for the headers length, no more headers.
ObjCStrongReference<NSMutableData> chunk(next_data_chunk(*inputStream, session, 0, lastChunk));
if (!chunk && !lastChunk) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "allocate the next memory chunk";
+ qCWarning(QT_BT_OSX) << "failed to allocate the next memory chunk";
return;
}
@@ -752,8 +749,7 @@ QT_USE_NAMESPACE
refCon:Q_NULLPTR];
if (status != kOBEXSuccess) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "send the next memory chunk";
+ qCWarning(QT_BT_OSX) << "failed to send the next memory chunk";
currentRequest = OBEXNoop;
if (delegate) // Response code is not important here.
delegate->OBEXPutError(kOBEXNoResourcesError, 0);
diff --git a/src/bluetooth/osx/osxbtperipheralmanager.mm b/src/bluetooth/osx/osxbtperipheralmanager.mm
new file mode 100644
index 00000000..9c443cf6
--- /dev/null
+++ b/src/bluetooth/osx/osxbtperipheralmanager.mm
@@ -0,0 +1,864 @@
+/****************************************************************************
+**
+** 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>
+
+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;
+}
+
+bool qt_validate_value_range(const QLowEnergyCharacteristicData &data)
+{
+ if (data.minimumValueLength() > data.maximumValueLength()
+ || data.minimumValueLength() < 0) {
+ return false;
+ }
+
+ return data.value().size() <= data.maximumValueLength();
+}
+
+}
+
+@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]);
+ maxNotificationValueLength = std::numeric_limits<NSUInteger>::max();
+ }
+
+ 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;
+
+ newQtService->endHandle = lastHandle;
+
+ 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) || !valueRanges.contains(charHandle)) {
+ emit notifier->CBManagerError(QLowEnergyController::UnknownError);
+ return;
+ }
+
+ const auto & range = valueRanges[charHandle];
+ if (value.size() < int(range.first) || value.size() > int(range.second)
+#ifdef Q_OS_IOS
+ || value.size() > OSXBluetooth::maxValueLength) {
+#else
+ ) {
+#endif
+ qCWarning(QT_BT_OSX) << "ignoring value of invalid length" << value.size();
+ return;
+ }
+
+ emit notifier->characteristicWritten(charHandle, value);
+
+ const auto nsData = mutable_data_from_bytearray(value);
+ charValues[charHandle] = nsData;
+ // We copy data here: sending update requests is async (see sendUpdateRequests),
+ // by the time we're allowed to actually send them, the data can change again
+ // and we'll send an 'out of order' value.
+ const ObjCStrongReference<NSData> copy([NSData dataWithData:nsData], true);
+ updateQueue.push_back(UpdateRequest{charHandle, copy});
+ [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 QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0)
+ if (peripheral.state == CBManagerStatePoweredOn) {
+#else
+ if (peripheral.state == CBPeripheralManagerStatePoweredOn) {
+#endif
+ // "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 QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0)
+ if (peripheral.state == CBManagerStateUnauthorized ||
+ peripheral.state == CBManagerStateUnsupported) {
+#else
+ if (peripheral.state == CBPeripheralManagerStateUnauthorized ||
+ peripheral.state == CBPeripheralManagerStateUnsupported) {
+#endif
+ 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];
+
+ if (const auto handle = charMap.key(characteristic))
+ emit notifier->notificationEnabled(handle, true);
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
+ didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic
+{
+ Q_UNUSED(characteristic)
+
+ if (peripheral != manager || !notifier)
+ return;
+
+ [self removeConnectedCentral:central];
+
+ if (![connectedCentrals count]) {
+ if (const auto handle = charMap.key(characteristic))
+ emit notifier->notificationEnabled(handle, false);
+ }
+}
+
+- (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)writeValueForCharacteristic:(QLowEnergyHandle) charHandle
+ withWriteRequest:(CBATTRequest *)request
+{
+ Q_ASSERT(charHandle);
+ Q_ASSERT(request);
+
+ Q_ASSERT(valueRanges.contains(charHandle));
+ const auto &range = valueRanges[charHandle];
+ Q_ASSERT(request.offset <= range.second
+ && request.value.length <= range.second - request.offset);
+
+ Q_ASSERT(charValues.contains(charHandle));
+ NSMutableData *const value = charValues[charHandle];
+ if (request.offset + request.value.length > value.length)
+ [value increaseLengthBy:request.offset + request.value.length - value.length];
+
+ [value replaceBytesInRange:NSMakeRange(request.offset, request.value.length)
+ withBytes:request.value.bytes];
+}
+
+- (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::map<QLowEnergyHandle, NSUInteger> updated;
+
+ for (CBATTRequest *request in requests) {
+ // Transition to 'connected' if needed.
+ [self addConnectedCentral:request.central];
+ const auto charHandle = charMap.key(request.characteristic);
+ const auto prevLen = updated[charHandle];
+ updated[charHandle] = std::max(request.offset + request.value.length,
+ prevLen);
+ [self writeValueForCharacteristic:charHandle withWriteRequest:request];
+ }
+
+ for (const auto pair : updated) {
+ const auto handle = pair.first;
+ NSMutableData *value = charValues[handle];
+ value.length = pair.second;
+ emit notifier->characteristicUpdated(handle, qt_bytearray(value));
+ const ObjCStrongReference<NSData> copy([NSData dataWithData:value],
+ true);
+ updateQueue.push_back(UpdateRequest{handle, copy});
+ }
+
+ if (requests.count) {
+ [manager respondToRequest:[requests objectAtIndex:0]
+ withResult:CBATTErrorSuccess];
+ }
+
+ [self sendUpdateRequests];
+}
+
+- (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();
+ if (charMap.contains(request.charHandle)) {
+ if ([connectedCentrals count]
+ && maxNotificationValueLength < [request.value length]) {
+ qCWarning(QT_BT_OSX) << "value of length" << [request.value length]
+ << "will possibly be truncated to"
+ << maxNotificationValueLength;
+ }
+ 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;
+ }
+
+ maxNotificationValueLength = std::min(maxNotificationValueLength,
+ central.maximumUpdateValueLength);
+
+ 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();
+ }
+
+ if (![connectedCentrals count])
+ maxNotificationValueLength = std::numeric_limits<NSUInteger>::max();
+}
+
+- (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()) {
+ if (!qt_validate_value_range(ch)) {
+ qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: "
+ "invalid value size/min-max length";
+ continue;
+ }
+
+#ifdef Q_OS_IOS
+ if (ch.value().length() > OSXBluetooth::maxValueLength) {
+ qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: "
+ "value exceeds the maximal permitted "
+ "value length (" << OSXBluetooth::maxValueLength
+ << "octets) on the platform";
+ continue;
+ }
+#endif
+
+ const auto cbChar(create_characteristic(ch));
+ if (!cbChar) {
+ qCWarning(QT_BT_OSX) << "addCharacteristicsAndDescritptors: "
+ "failed to allocate a characteristic";
+ continue;
+ }
+
+ const auto nsData(mutable_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] = nsData;
+ valueRanges[declHandle] = ValueRange(ch.minimumValueLength(), ch.maximumValueLength());
+ // QT part:
+ QLowEnergyServicePrivate::CharData charData;
+ charData.valueHandle = declHandle;
+ 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;
+
+ Q_ASSERT(valueRanges.contains(handle));
+
+ const auto &range = valueRanges[handle];
+ if (request.offset > range.second)
+ return CBATTErrorInvalidOffset;
+
+ if (request.value.length > range.second - request.offset)
+ return CBATTErrorInvalidAttributeValueLength;
+
+ 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..19a831d8
--- /dev/null
+++ b/src/bluetooth/osx/osxbtperipheralmanager_p.h
@@ -0,0 +1,190 @@
+/****************************************************************************
+**
+** 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
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of internal files. This header file may change from version to version
+// without notice, or even be removed.
+//
+// We mean it.
+//
+
+#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/qpair.h>
+#include <QtCore/qmap.h>
+
+#include <vector>
+#include <deque>
+#include <map>
+
+#include <Foundation/Foundation.h>
+
+#include "osxbluetooth_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;
+};
+
+using ValueRange = QPair<NSUInteger, NSUInteger>;
+
+@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<NSMutableData>> charValues;
+
+ QMap<QLowEnergyHandle, ValueRange> valueRanges;
+
+ std::deque<UpdateRequest> updateQueue;
+
+ ObjCScopedPointer<NSMutableSet> connectedCentrals;
+
+ PeripheralState state;
+ NSUInteger maxNotificationValueLength;
+}
+
+- (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/osxbtrfcommchannel.mm b/src/bluetooth/osx/osxbtrfcommchannel.mm
index e929f335..ea679ec8 100644
--- a/src/bluetooth/osx/osxbtrfcommchannel.mm
+++ b/src/bluetooth/osx/osxbtrfcommchannel.mm
@@ -97,13 +97,13 @@ QT_USE_NAMESPACE
withChannelID:(BluetoothRFCOMMChannelID)channelID
{
if (address.isNull()) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "invalid peer address";
+ qCCritical(QT_BT_OSX) << "invalid peer address";
return kIOReturnNoDevice;
}
// Can never be called twice.
if (connected || device || channel) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "connection is already active";
+ qCCritical(QT_BT_OSX) << "connection is already active";
return kIOReturnStillOpen;
}
@@ -112,14 +112,14 @@ QT_USE_NAMESPACE
const BluetoothDeviceAddress iobtAddress = OSXBluetooth::iobluetooth_address(address);
device = [IOBluetoothDevice deviceWithAddress:&iobtAddress];
if (!device) { // TODO: do I always check this BTW??? Apple's docs say nothing about nil.
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create a device";
+ qCCritical(QT_BT_OSX) << "failed to create a device";
return kIOReturnNoDevice;
}
const IOReturn status = [device openRFCOMMChannelAsync:&channel
withChannelID:channelID delegate:self];
if (status != kIOReturnSuccess) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to open L2CAP channel";
+ qCCritical(QT_BT_OSX) << "failed to open L2CAP channel";
// device is still autoreleased.
device = nil;
return status;
diff --git a/src/bluetooth/osx/osxbtsdpinquiry.mm b/src/bluetooth/osx/osxbtsdpinquiry.mm
index a1d6af1c..a0bfdeef 100644
--- a/src/bluetooth/osx/osxbtsdpinquiry.mm
+++ b/src/bluetooth/osx/osxbtsdpinquiry.mm
@@ -184,7 +184,7 @@ using namespace OSXBluetooth;
if (qtFilters.size()) {
array.reset([[NSMutableArray alloc] init]);
if (!array) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate an uuid filter";
+ qCCritical(QT_BT_OSX) << "failed to allocate an uuid filter";
return kIOReturnError;
}
@@ -195,7 +195,7 @@ using namespace OSXBluetooth;
}
if (int([array count]) != qtFilters.size()) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create an uuid filter";
+ qCCritical(QT_BT_OSX) << "failed to create an uuid filter";
return kIOReturnError;
}
}
@@ -203,7 +203,7 @@ using namespace OSXBluetooth;
const BluetoothDeviceAddress iobtAddress(iobluetooth_address(address));
ObjCScopedPointer<IOBluetoothDevice> newDevice([[IOBluetoothDevice deviceWithAddress:&iobtAddress] retain]);
if (!newDevice) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create an IOBluetoothDevice object";
+ qCCritical(QT_BT_OSX) << "failed to create an IOBluetoothDevice object";
return kIOReturnError;
}
@@ -217,7 +217,7 @@ using namespace OSXBluetooth;
result = [device performSDPQuery:self];
if (result != kIOReturnSuccess) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to start an SDP query";
+ qCCritical(QT_BT_OSX) << "failed to start an SDP query";
device = oldDevice.take();
} else {
isActive = true;
diff --git a/src/bluetooth/osx/osxbtutility.mm b/src/bluetooth/osx/osxbtutility.mm
index 942fed1c..1508c89f 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"
@@ -70,6 +71,10 @@ Q_LOGGING_CATEGORY(QT_BT_OSX, "qt.bluetooth.ios")
namespace OSXBluetooth {
+const int defaultLEScanTimeoutMS = 25000;
+// We use it only on iOS for now:
+const int maxValueLength = 512;
+
QString qt_address(NSString *address)
{
if (address && address.length) {
@@ -314,6 +319,19 @@ ObjCStrongReference<NSData> data_from_bytearray(const QByteArray & qtData)
return result;
}
+ObjCStrongReference<NSMutableData> mutable_data_from_bytearray(const QByteArray &qtData)
+{
+ using MutableData = ObjCStrongReference<NSMutableData>;
+
+ if (!qtData.size())
+ return MutableData([[NSMutableData alloc] init], false);
+
+ MutableData result([[NSMutableData alloc] initWithLength:qtData.size()], false);
+ [result replaceBytesInRange:NSMakeRange(0, qtData.size())
+ withBytes:qtData.constData()];
+ return result;
+}
+
// A small RAII class for a dispatch queue.
class SerialDispatchQueue
{
@@ -350,11 +368,6 @@ dispatch_queue_t qt_LE_queue()
return leQueue.data();
}
-unsigned qt_LE_deviceInquiryLength()
-{
- return 10;
-}
-
}
QT_END_NAMESPACE
diff --git a/src/bluetooth/osx/osxbtutility_p.h b/src/bluetooth/osx/osxbtutility_p.h
index de3d3ea2..148ebc0b 100644
--- a/src/bluetooth/osx/osxbtutility_p.h
+++ b/src/bluetooth/osx/osxbtutility_p.h
@@ -55,13 +55,14 @@
#include <QtCore/qloggingcategory.h>
#include <QtCore/qscopedpointer.h>
-#include <QtCore/qsysinfo.h>
#include <QtCore/qglobal.h>
#include <Foundation/Foundation.h>
QT_BEGIN_NAMESPACE
+class QLowEnergyCharacteristicData;
+class QBluetoothAddress;
class QBluetoothUuid;
namespace OSXBluetooth {
@@ -280,7 +281,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);
@@ -296,22 +297,14 @@ bool equal_uuids(const QBluetoothUuid &qtUuid, CBUUID *cbUuid);
bool equal_uuids(CBUUID *cbUuid, const QBluetoothUuid &qtUuid);
QByteArray qt_bytearray(NSData *data);
QByteArray qt_bytearray(NSObject *data);
-ObjCStrongReference<NSData> data_from_bytearray(const QByteArray & qtData);
-inline QSysInfo::MacVersion qt_OS_limit(QSysInfo::MacVersion osxVersion, QSysInfo::MacVersion iosVersion)
-{
-#ifdef Q_OS_OSX
- Q_UNUSED(iosVersion)
- return osxVersion;
-#else
- Q_UNUSED(osxVersion)
- return iosVersion;
-#endif
-}
+ObjCStrongReference<NSData> data_from_bytearray(const QByteArray &qtData);
+ObjCStrongReference<NSMutableData> mutable_data_from_bytearray(const QByteArray &qtData);
dispatch_queue_t qt_LE_queue();
-// LE scan, in seconds.
-unsigned qt_LE_deviceInquiryLength();
+
+extern const int defaultLEScanTimeoutMS;
+extern const int maxValueLength;
} // namespace OSXBluetooth
diff --git a/src/bluetooth/qbluetooth.cpp b/src/bluetooth/qbluetooth.cpp
index 483fe65a..37a4774d 100644
--- a/src/bluetooth/qbluetooth.cpp
+++ b/src/bluetooth/qbluetooth.cpp
@@ -101,5 +101,6 @@ namespace QBluetooth {
Q_LOGGING_CATEGORY(QT_BT, "qt.bluetooth")
Q_LOGGING_CATEGORY(QT_BT_ANDROID, "qt.bluetooth.android")
Q_LOGGING_CATEGORY(QT_BT_BLUEZ, "qt.bluetooth.bluez")
+Q_LOGGING_CATEGORY(QT_BT_WINRT, "qt.bluetooth.winphone")
QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent.cpp
index b6faee75..b033ae3c 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent.cpp
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent.cpp
@@ -40,9 +40,12 @@
#include "qbluetoothdevicediscoveryagent.h"
#include "qbluetoothdevicediscoveryagent_p.h"
+#include <QtCore/qloggingcategory.h>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(QT_BT)
+
/*!
\class QBluetoothDeviceDiscoveryAgent
\inmodule QtBluetooth
@@ -88,6 +91,8 @@ QT_BEGIN_NAMESPACE
platform. The error is set in response to a call to \l start().
An example for such cases are iOS versions below 5.0 which do not support
Bluetooth device search at all. This value was introduced by Qt 5.5.
+ \value UnsupportedDiscoveryMethod One of the requested discovery methods is not supported by
+ the current platform. This value was introduced by Qt 5.8.
\value UnknownError An unknown error has occurred.
*/
@@ -110,6 +115,22 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ \enum QBluetoothDeviceDiscoveryAgent::DiscoveryMethod
+
+ This enum descibes the type of discovery method employed by the QBluetoothDeviceDiscoveryAgent.
+
+ \value NoMethod The discovery is not possible. None of the available
+ methods are supported.
+ \value ClassicMethod The discovery process searches for Bluetooth Classic
+ (BaseRate) devices.
+ \value LowEnergyMethod The discovery process searches for Bluetooth Low Energy
+ devices.
+
+ \sa supportedDiscoveryMethods()
+ \since 5.8
+*/
+
+/*!
\fn void QBluetoothDeviceDiscoveryAgent::deviceDiscovered(const QBluetoothDeviceInfo &info)
This signal is emitted when the Bluetooth device described by \a info is discovered.
@@ -232,16 +253,106 @@ QList<QBluetoothDeviceInfo> QBluetoothDeviceDiscoveryAgent::discoveredDevices()
}
/*!
+ Sets the maximum search time for Bluetooth Low Energy device search to
+ \a timeout in milliseconds. If \a timeout is \c 0 the discovery runs
+ until \l stop() is called.
+
+ This reflects the fact that the discovery process for Bluetooth Low Energy devices
+ is mostly open ended. The platform continues to look for more devices until the search is
+ manually stopped. The timeout ensures that the search is aborted after \a timeout milliseconds.
+ Of course, it is still possible to manually abort the discovery by calling \l stop().
+
+ The new timeout value does not take effect until the device search is restarted.
+ In addition the timeout does not affect the classic Bluetooth device search. Depending on
+ the platform the classic search may add more time to the total discovery process
+ beyond \a timeout.
+
+ \sa lowEnergyDiscoveryTimeout()
+ \since 5.8
+ */
+void QBluetoothDeviceDiscoveryAgent::setLowEnergyDiscoveryTimeout(int timeout)
+{
+ Q_D(QBluetoothDeviceDiscoveryAgent);
+
+ // cannot deliberately turn it off
+ if (d->lowEnergySearchTimeout < 0 || timeout < 0) {
+ qCDebug(QT_BT) << "The Bluetooth Low Energy device discovery timeout cannot be negative "
+ "or set on a backend which does not support this feature.";
+ return;
+ }
+
+ d->lowEnergySearchTimeout = timeout;
+}
+
+/*!
+ Returns a timeout in milliseconds that is applied to the Bluetooth Low Energy device search.
+ A value of \c -1 implies that the platform does not support this property and the timeout for
+ the device search cannot be adjusted. A return value of \c 0
+ implies a never-ending search which must be manually stopped via \l stop().
+
+ \sa setLowEnergyDiscoveryTimeout()
+ \since 5.8
+ */
+int QBluetoothDeviceDiscoveryAgent::lowEnergyDiscoveryTimeout() const
+{
+ Q_D(const QBluetoothDeviceDiscoveryAgent);
+ return d->lowEnergySearchTimeout;
+}
+
+/*!
+ \fn QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
+
+ This function returns the discovery methods supported by the current platform.
+ It can be used to limit the scope of the device discovery.
+
+ \since 5.8
+*/
+
+/*!
Starts Bluetooth device discovery, if it is not already started.
The deviceDiscovered() signal is emitted as each device is discovered. The finished() signal
- is emitted once device discovery is complete.
+ is emitted once device discovery is complete. The discovery utilizes the maximum set of
+ supported discovery methods on the platform.
+
+ \sa supportedDiscoveryMethods()
*/
void QBluetoothDeviceDiscoveryAgent::start()
{
Q_D(QBluetoothDeviceDiscoveryAgent);
if (!isActive() && d->lastError != InvalidBluetoothAdapterError)
- d->start();
+ d->start(supportedDiscoveryMethods());
+}
+
+/*!
+ Start Bluetooth device discovery, if it is not already started and the provided
+ \a methods are supported.
+ The discovery \a methods limit the scope of the device search.
+ For example, if the target service or device is a Bluetooth Low Energy device,
+ this function could be used to limit the search to Bluetooth Low Energy devices and
+ thereby reduces the discovery time significantly.
+
+ \since 5.8
+*/
+void QBluetoothDeviceDiscoveryAgent::start(DiscoveryMethods methods)
+{
+ if (methods == NoMethod)
+ return;
+
+ DiscoveryMethods supported =
+ QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods();
+
+ Q_D(QBluetoothDeviceDiscoveryAgent);
+ if (!((supported & methods) == methods)) {
+ d->lastError = UnsupportedDiscoveryMethod;
+ d->errorString = QBluetoothDeviceDiscoveryAgent::tr("One or more device discovery methods "
+ "are not supported on this platform");
+ emit error(d->lastError);
+ return;
+ }
+
+ if (!isActive() && d->lastError != InvalidBluetoothAdapterError)
+ d->start(methods);
}
/*!
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent.h b/src/bluetooth/qbluetoothdevicediscoveryagent.h
index 954ae704..84087605 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent.h
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent.h
@@ -65,6 +65,7 @@ public:
PoweredOffError,
InvalidBluetoothAdapterError,
UnsupportedPlatformError,
+ UnsupportedDiscoveryMethod,
UnknownError = 100 // New errors must be added before Unknown error
};
Q_ENUM(Error)
@@ -75,6 +76,15 @@ public:
};
Q_ENUM(InquiryType)
+ enum DiscoveryMethod
+ {
+ NoMethod = 0x0,
+ ClassicMethod = 0x01,
+ LowEnergyMethod = 0x02,
+ };
+ Q_DECLARE_FLAGS(DiscoveryMethods, DiscoveryMethod)
+ Q_FLAG(DiscoveryMethods)
+
explicit QBluetoothDeviceDiscoveryAgent(QObject *parent = Q_NULLPTR);
explicit QBluetoothDeviceDiscoveryAgent(const QBluetoothAddress &deviceAdapter,
QObject *parent = Q_NULLPTR);
@@ -91,8 +101,13 @@ public:
QList<QBluetoothDeviceInfo> discoveredDevices() const;
+ void setLowEnergyDiscoveryTimeout(int msTimeout);
+ int lowEnergyDiscoveryTimeout() const;
+
+ static DiscoveryMethods supportedDiscoveryMethods();
public Q_SLOTS:
void start();
+ void start(DiscoveryMethods method);
void stop();
Q_SIGNALS:
@@ -116,6 +131,8 @@ private:
#endif
};
+Q_DECLARE_OPERATORS_FOR_FLAGS(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods)
+
QT_END_NAMESPACE
#endif
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
index 1a5df9e7..e76ddff7 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
@@ -45,6 +45,7 @@
#include <QtCore/private/qjnihelpers_p.h>
#include "android/devicediscoverybroadcastreceiver_p.h"
#include <QtAndroidExtras/QAndroidJniEnvironment>
+#include <QtAndroid>
QT_BEGIN_NAMESPACE
@@ -66,6 +67,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
leScanTimeout(0),
pendingCancel(false),
pendingStart(false),
+ lowEnergySearchTimeout(25000),
q_ptr(parent)
{
QAndroidJniEnvironment env;
@@ -101,8 +103,16 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
return m_active != NoScanActive;
}
-void QBluetoothDeviceDiscoveryAgentPrivate::start()
+QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
{
+ return (LowEnergyMethod | ClassicMethod);
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
+{
+ //TODO Implement discovery method handling (see input parameter)
+ requestedMethods = methods;
+
if (pendingCancel) {
pendingStart = true;
return;
@@ -136,6 +146,34 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start()
return;
}
+ // check Android v23+ permissions
+ // -> BTLE search requires android.permission.ACCESS_COARSE_LOCATION
+ if (requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod
+ && QtAndroid::androidSdkVersion() >= 23)
+ {
+ QString permission(QLatin1String("android.permission.ACCESS_COARSE_LOCATION"));
+
+ // do we have required permission already, if so nothing to do
+ if (QtAndroidPrivate::checkPermission(permission) == QtAndroidPrivate::PermissionsResult::Denied) {
+ qCWarning(QT_BT_ANDROID) << "Requesting ACCESS_COARSE_LOCATION permission";
+
+ QAndroidJniEnvironment env;
+ const QHash<QString, QtAndroidPrivate::PermissionsResult> results =
+ QtAndroidPrivate::requestPermissionsSync(env, QStringList() << permission);
+ if (!results.contains(permission)
+ || results[permission] == QtAndroidPrivate::PermissionsResult::Denied)
+ {
+ qCWarning(QT_BT_ANDROID) << "Search not possible due to missing permission (ACCESS_COARSE_LOCATION)";
+ lastError = QBluetoothDeviceDiscoveryAgent::UnknownError;
+ errorString = QBluetoothDeviceDiscoveryAgent::tr("Missing Location permission. Search is not possible");
+ emit q->error(lastError);
+ return;
+ }
+ }
+
+ qCWarning(QT_BT_ANDROID) << "ACCESS_COARSE_LOCATION permission available";
+ }
+
// install Java BroadcastReceiver
if (!receiver) {
// SDP based device discovery
@@ -148,18 +186,35 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start()
discoveredDevices.clear();
- const bool success = adapter.callMethod<jboolean>("startDiscovery");
- if (!success) {
- lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
- errorString = QBluetoothDeviceDiscoveryAgent::tr("Discovery cannot be started");
- emit q->error(lastError);
- return;
- }
+ // by arbitrary definition we run classic search first
+ if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) {
+ const bool success = adapter.callMethod<jboolean>("startDiscovery");
+ if (!success) {
+ lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
+ errorString = QBluetoothDeviceDiscoveryAgent::tr("Classic Discovery cannot be started");
+ emit q->error(lastError);
+ return;
+ }
- m_active = SDPScanActive;
+ m_active = SDPScanActive;
+ qCDebug(QT_BT_ANDROID)
+ << "QBluetoothDeviceDiscoveryAgentPrivate::start() - Classic search successfully started.";
+ } else {
+ // LE search only requested
+ Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
- qCDebug(QT_BT_ANDROID)
- << "QBluetoothDeviceDiscoveryAgentPrivate::start() - successfully executed.";
+ if (QtAndroidPrivate::androidSdkVersion() < 18) {
+ qCDebug(QT_BT_ANDROID) << "Skipping Bluetooth Low Energy device scan due to"
+ "insufficient Android version.";
+ m_active = NoScanActive;
+ lastError = QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod;
+ errorString = QBluetoothDeviceDiscoveryAgent::tr("Low Energy Discovery not supported");
+ emit q->error(lastError);
+ return;
+ }
+
+ startLowEnergyScan();
+ }
}
void QBluetoothDeviceDiscoveryAgentPrivate::stop()
@@ -200,7 +255,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::processSdpDiscoveryFinished()
emit q->canceled();
} else if (pendingStart) {
pendingStart = pendingCancel = false;
- start();
+ start(requestedMethods);
} else {
// check that it didn't finish due to turned off Bluetooth Device
const int state = adapter.callMethod<jint>("getState");
@@ -212,6 +267,13 @@ void QBluetoothDeviceDiscoveryAgentPrivate::processSdpDiscoveryFinished()
return;
}
+ // no BTLE scan requested
+ if (!(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)) {
+ m_active = NoScanActive;
+ emit q->finished();
+ return;
+ }
+
// start LE scan if supported
if (QtAndroidPrivate::androidSdkVersion() < 18) {
qCDebug(QT_BT_ANDROID) << "Skipping Bluetooth Low Energy device scan";
@@ -293,15 +355,21 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startLowEnergyScan()
return;
}
+ // wait interval and sum up what was found
if (!leScanTimeout) {
leScanTimeout = new QTimer(this);
leScanTimeout->setSingleShot(true);
- leScanTimeout->setInterval(25000);
connect(leScanTimeout, &QTimer::timeout,
this, &QBluetoothDeviceDiscoveryAgentPrivate::stopLowEnergyScan);
}
- leScanTimeout->start();
+ if (lowEnergySearchTimeout > 0) { // otherwise no timeout and stop() required
+ leScanTimeout->setInterval(lowEnergySearchTimeout);
+ leScanTimeout->start();
+ }
+
+ qCDebug(QT_BT_ANDROID)
+ << "QBluetoothDeviceDiscoveryAgentPrivate::start() - Low Energy search successfully started.";
}
void QBluetoothDeviceDiscoveryAgentPrivate::stopLowEnergyScan()
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp
index 0243d31f..5288eaf8 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp
@@ -68,10 +68,12 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
adapterBluez5(0),
discoveryTimer(0),
useExtendedDiscovery(false),
+ lowEnergySearchTimeout(-1), // remains -1 on BlueZ 4 -> timeout not supported
q_ptr(parent)
{
Q_Q(QBluetoothDeviceDiscoveryAgent);
if (isBluez5()) {
+ lowEnergySearchTimeout = 20000;
managerBluez5 = new OrgFreedesktopDBusObjectManagerInterface(
QStringLiteral("org.bluez"),
QStringLiteral("/"),
@@ -115,8 +117,17 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
return (adapter || adapterBluez5);
}
-void QBluetoothDeviceDiscoveryAgentPrivate::start()
+QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
{
+ return (ClassicMethod | LowEnergyMethod);
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods /*methods*/)
+{
+ // Currently both BlueZ backends do not distinguish discovery methods.
+ // The DBus API's always return both device types. Therefore we ignore
+ // the passed in methods.
+
if (pendingCancel == true) {
pendingStart = true;
return;
@@ -273,16 +284,18 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startBluez5()
}
}
- // wait 20s and sum up what was found
+ // wait interval and sum up what was found
if (!discoveryTimer) {
discoveryTimer = new QTimer(q);
discoveryTimer->setSingleShot(true);
- discoveryTimer->setInterval(20000); // 20s
QObject::connect(discoveryTimer, SIGNAL(timeout()),
q, SLOT(_q_discoveryFinished()));
}
- discoveryTimer->start();
+ if (lowEnergySearchTimeout > 0) { // otherwise no timeout and stop() required
+ discoveryTimer->setInterval(lowEnergySearchTimeout);
+ discoveryTimer->start();
+ }
}
void QBluetoothDeviceDiscoveryAgentPrivate::stop()
@@ -436,7 +449,9 @@ void QBluetoothDeviceDiscoveryAgentPrivate::_q_propertyChanged(const QString &na
pendingStart = false;
pendingCancel = false;
- start();
+ // start parameter ignored since Bluez 4 doesn't distinguish them
+ start(QBluetoothDeviceDiscoveryAgent::ClassicMethod
+ | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
} else {
// happens when agent is created while other agent called StopDiscovery()
if (!adapter)
@@ -514,7 +529,8 @@ void QBluetoothDeviceDiscoveryAgentPrivate::_q_discoveryFinished()
} else if (pendingStart) {
pendingStart = false;
pendingCancel = false;
- start();
+ start(QBluetoothDeviceDiscoveryAgent::ClassicMethod
+ | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
} else {
emit q->finished();
}
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm
index 0e4b460f..3f4c6755 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm
@@ -37,16 +37,17 @@
**
****************************************************************************/
-#include "qbluetoothdevicediscoverytimer_osx_p.h"
#include "qbluetoothdevicediscoveryagent.h"
#include "osx/osxbtledeviceinquiry_p.h"
#include "qbluetoothlocaldevice.h"
#include "qbluetoothdeviceinfo.h"
+#include "osx/osxbtnotifier_p.h"
#include "osx/osxbtutility_p.h"
#include "osx/uistrings_p.h"
#include "qbluetoothuuid.h"
#include <QtCore/qloggingcategory.h>
+#include <QtCore/qobject.h>
#include <QtCore/qglobal.h>
#include <QtCore/qstring.h>
#include <QtCore/qdebug.h>
@@ -56,12 +57,24 @@
QT_BEGIN_NAMESPACE
-using OSXBluetooth::ObjCScopedPointer;
+namespace
+{
+
+void registerQDeviceDiscoveryMetaType()
+{
+ static bool initDone = false;
+ if (!initDone) {
+ qRegisterMetaType<QBluetoothDeviceInfo>();
+ qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>();
+ initDone = true;
+ }
+}
+
+}//namespace
-class QBluetoothDeviceDiscoveryAgentPrivate
+class QBluetoothDeviceDiscoveryAgentPrivate : public QObject
{
friend class QBluetoothDeviceDiscoveryAgent;
- friend class OSXBluetooth::DDATimerHandler;
public:
QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &address,
@@ -70,17 +83,16 @@ public:
bool isActive() const;
- void start();
+ void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods m);
void stop();
private:
- typedef QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) LEDeviceInquiryObjC;
+ using LEDeviceInquiryObjC = QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry);
void LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error);
void LEnotSupported();
void LEdeviceFound(const QBluetoothDeviceInfo &info);
void LEinquiryFinished();
- void checkLETimeout();
void setError(QBluetoothDeviceDiscoveryAgent::Error, const QString &text = QString());
@@ -91,63 +103,30 @@ private:
QBluetoothDeviceDiscoveryAgent::InquiryType inquiryType;
- typedef ObjCScopedPointer<LEDeviceInquiryObjC> LEDeviceInquiry;
+ using LEDeviceInquiry = OSXBluetooth::ObjCScopedPointer<LEDeviceInquiryObjC>;
LEDeviceInquiry inquiryLE;
- typedef QList<QBluetoothDeviceInfo> DevicesList;
+ using DevicesList = QList<QBluetoothDeviceInfo>;
DevicesList discoveredDevices;
bool startPending;
bool stopPending;
- QScopedPointer<OSXBluetooth::DDATimerHandler> timer;
+ int lowEnergySearchTimeout;
};
-namespace OSXBluetooth {
-
-DDATimerHandler::DDATimerHandler(QBluetoothDeviceDiscoveryAgentPrivate *d)
- : owner(d)
-{
- Q_ASSERT_X(owner, Q_FUNC_INFO, "invalid pointer");
-
- timer.setSingleShot(false);
- connect(&timer, &QTimer::timeout, this, &DDATimerHandler::onTimer);
-}
-
-void DDATimerHandler::start(int msec)
-{
- Q_ASSERT_X(msec > 0, Q_FUNC_INFO, "invalid time interval");
- if (timer.isActive()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "timer is active";
- return;
- }
-
- timer.start(msec);
-}
-
-void DDATimerHandler::stop()
-{
- timer.stop();
-}
-
-void DDATimerHandler::onTimer()
-{
- Q_ASSERT(owner);
- owner->checkLETimeout();
-}
-
-}
-
QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &adapter,
QBluetoothDeviceDiscoveryAgent *q) :
q_ptr(q),
lastError(QBluetoothDeviceDiscoveryAgent::NoError),
inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry),
startPending(false),
- stopPending(false)
+ stopPending(false),
+ lowEnergySearchTimeout(OSXBluetooth::defaultLEScanTimeoutMS)
{
Q_UNUSED(adapter);
+ registerQDeviceDiscoveryMetaType();
Q_ASSERT_X(q != Q_NULLPTR, Q_FUNC_INFO, "invalid q_ptr (null)");
}
@@ -158,7 +137,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) {
// Local variable to be retained ...
LEDeviceInquiryObjC *inq = inquiryLE.data();
- dispatch_async(leQueue, ^{
+ dispatch_sync(leQueue, ^{
[inq stop];
});
}
@@ -175,7 +154,7 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
return inquiryLE;
}
-void QBluetoothDeviceDiscoveryAgentPrivate::start()
+void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods /*methods*/)
{
Q_ASSERT_X(!isActive(), Q_FUNC_INFO, "called on active device discovery agent");
@@ -186,26 +165,37 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start()
using namespace OSXBluetooth;
- inquiryLE.reset([[LEDeviceInquiryObjC alloc] init]);
+ QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier);
+ // Connections:
+ using ErrMemFunPtr = void (LECBManagerNotifier::*)(QBluetoothDeviceDiscoveryAgent::Error);
+ notifier->connect(notifier.data(), ErrMemFunPtr(&LECBManagerNotifier::CBManagerError),
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError);
+ notifier->connect(notifier.data(), &LECBManagerNotifier::LEnotSupported,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported);
+ notifier->connect(notifier.data(), &LECBManagerNotifier::discoveryFinished,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished);
+ notifier->connect(notifier.data(), &LECBManagerNotifier::deviceDiscovered,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceFound);
+
+ inquiryLE.reset([[LEDeviceInquiryObjC alloc] initWithNotifier:notifier.data()]);
+ if (inquiryLE)
+ notifier.take(); // Whatever happens next, inquiryLE is already the owner ...
+
dispatch_queue_t leQueue(qt_LE_queue());
if (!leQueue || !inquiryLE) {
setError(QBluetoothDeviceDiscoveryAgent::UnknownError,
QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED));
emit q_ptr->error(lastError);
+ return;
}
discoveredDevices.clear();
setError(QBluetoothDeviceDiscoveryAgent::NoError);
- // CoreBluetooth does not have a timeout. We start a timer here
- // and check if scan really started and if yes if we have a timeout.
- timer.reset(new OSXBluetooth::DDATimerHandler(this));
- timer->start(2000);
-
// Create a local variable - to have a strong referece in a block.
LEDeviceInquiryObjC *inq = inquiryLE.data();
dispatch_async(leQueue, ^{
- [inq start];
+ [inq startWithTimeout:lowEnergySearchTimeout];
});
}
@@ -223,7 +213,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop()
// Create a local variable - to have a strong referece in a block.
LEDeviceInquiryObjC *inq = inquiryLE.data();
- dispatch_async(leQueue, ^{
+ dispatch_sync(leQueue, ^{
[inq stop];
});
// We consider LE scan to be stopped immediately and
@@ -241,7 +231,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDisco
Q_FUNC_INFO, "unexpected error");
inquiryLE.reset();
- timer->stop();
startPending = false;
stopPending = false;
@@ -252,7 +241,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDisco
void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported()
{
inquiryLE.reset();
- timer->stop();
startPending = false;
stopPending = false;
@@ -281,7 +269,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceFound(const QBluetoothDevice
void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished()
{
inquiryLE.reset();
- timer->stop();
if (stopPending && !startPending) {
stopPending = false;
@@ -289,47 +276,14 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished()
} else if (startPending) {
startPending = false;
stopPending = false;
- start();
+ // always the same method for start() on iOS
+ // classic search not supported
+ start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
} else {
emit q_ptr->finished();
}
}
-void QBluetoothDeviceDiscoveryAgentPrivate::checkLETimeout()
-{
- Q_ASSERT_X(inquiryLE, Q_FUNC_INFO, "LE device inquiry is nil");
-
- using namespace OSXBluetooth;
-
- const LEInquiryState state([inquiryLE inquiryState]);
- if (state == InquiryStarting || state == InquiryActive)
- return; // Wait ...
-
- if (state == ErrorPoweredOff)
- return LEinquiryError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
-
- if (state == ErrorLENotSupported)
- return LEnotSupported();
-
- if (state == InquiryFinished) {
- // Process found devices if any ...
- const QList<QBluetoothDeviceInfo> leDevices([inquiryLE discoveredDevices]);
- foreach (const QBluetoothDeviceInfo &info, leDevices) {
- // We were cancelled on a previous device discovered signal ...
- if (!inquiryLE)
- break;
- LEdeviceFound(info);
- }
-
- if (inquiryLE)
- LEinquiryFinished();
- return;
- }
-
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected inquiry state in LE timeout";
- // Actually, this deserves an assert :)
-}
-
void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error,
const QString &text)
{
@@ -373,7 +327,7 @@ QBluetoothDeviceDiscoveryAgent::QBluetoothDeviceDiscoveryAgent(
d_ptr(new QBluetoothDeviceDiscoveryAgentPrivate(deviceAdapter, this))
{
if (!deviceAdapter.isNull()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "local device address is "
+ qCWarning(QT_BT_OSX) << "local device address is "
"not available, provided address is ignored";
d_ptr->setError(InvalidBluetoothAdapterError);
}
@@ -399,14 +353,39 @@ QList<QBluetoothDeviceInfo> QBluetoothDeviceDiscoveryAgent::discoveredDevices()
return d_ptr->discoveredDevices;
}
+QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
+{
+ return LowEnergyMethod;
+}
+
void QBluetoothDeviceDiscoveryAgent::start()
{
if (d_ptr->lastError != InvalidBluetoothAdapterError) {
if (!isActive())
- d_ptr->start();
+ d_ptr->start(supportedDiscoveryMethods());
else
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "already started";
+ qCDebug(QT_BT_OSX) << "already started";
+ }
+}
+
+void QBluetoothDeviceDiscoveryAgent::start(DiscoveryMethods methods)
+{
+ if (methods == NoMethod)
+ return;
+
+ DiscoveryMethods supported =
+ QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods();
+
+ if (!((supported & methods) == methods)) {
+ d_ptr->lastError = UnsupportedDiscoveryMethod;
+ d_ptr->errorString = QBluetoothDeviceDiscoveryAgent::tr("One or more device discovery methods "
+ "are not supported on this platform");
+ emit error(d_ptr->lastError);
+ return;
}
+
+ if (!isActive() && d_ptr->lastError != InvalidBluetoothAdapterError)
+ d_ptr->start(methods);
}
void QBluetoothDeviceDiscoveryAgent::stop()
@@ -430,4 +409,21 @@ QString QBluetoothDeviceDiscoveryAgent::errorString() const
return d_ptr->errorString;
}
+int QBluetoothDeviceDiscoveryAgent::lowEnergyDiscoveryTimeout() const
+{
+ return d_ptr->lowEnergySearchTimeout;
+}
+
+void QBluetoothDeviceDiscoveryAgent::setLowEnergyDiscoveryTimeout(int timeout)
+{
+ // cannot deliberately turn it off
+ if (timeout < 0) {
+ qCDebug(QT_BT_OSX) << "The Bluetooth Low Energy device discovery timeout cannot be negative.";
+ return;
+ }
+
+ d_ptr->lowEnergySearchTimeout = timeout;
+ return;
+}
+
QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm
index 63eab2b9..b308f7cc 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm
@@ -37,22 +37,23 @@
**
****************************************************************************/
-#include "qbluetoothdevicediscoverytimer_osx_p.h"
#include "qbluetoothdevicediscoveryagent.h"
#include "osx/osxbtledeviceinquiry_p.h"
#include "osx/osxbtdeviceinquiry_p.h"
#include "qbluetoothlocaldevice.h"
#include "osx/osxbtsdpinquiry_p.h"
#include "qbluetoothdeviceinfo.h"
+#include "osx/osxbtnotifier_p.h"
#include "osx/osxbtutility_p.h"
#include "osx/osxbluetooth_p.h"
#include "osx/uistrings_p.h"
#include "qbluetoothhostinfo.h"
+#include "qbluetoothaddress.h"
+#include "osx/uistrings_p.h"
#include "qbluetoothuuid.h"
#include <QtCore/qloggingcategory.h>
#include <QtCore/qscopedpointer.h>
-#include <QtCore/qdatetime.h>
#include <QtCore/qglobal.h>
#include <QtCore/qstring.h>
#include <QtCore/qdebug.h>
@@ -62,23 +63,40 @@
QT_BEGIN_NAMESPACE
-using OSXBluetooth::ObjCScopedPointer;
+namespace
+{
+
+void registerQDeviceDiscoveryMetaType()
+{
+ static bool initDone = false;
+ if (!initDone) {
+ qRegisterMetaType<QBluetoothDeviceInfo>();
+ qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>();
+ initDone = true;
+ }
+}
+
+}//namespace
-class QBluetoothDeviceDiscoveryAgentPrivate : public OSXBluetooth::DeviceInquiryDelegate
+class QBluetoothDeviceDiscoveryAgentPrivate : public QObject,
+ public OSXBluetooth::DeviceInquiryDelegate
{
friend class QBluetoothDeviceDiscoveryAgent;
- friend class OSXBluetooth::DDATimerHandler;
public:
- typedef QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) LEDeviceInquiryObjC;
+ template<class T>
+ using ObjCScopedPointer = OSXBluetooth::ObjCScopedPointer<T>;
+ using LEDeviceInquiryObjC = QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry);
QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress & address,
QBluetoothDeviceDiscoveryAgent *q);
- virtual ~QBluetoothDeviceDiscoveryAgentPrivate();
+
+ ~QBluetoothDeviceDiscoveryAgentPrivate() Q_DECL_OVERRIDE;
bool isValid() const;
bool isActive() const;
- void start();
+ void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods);
+ void startClassic();
void startLE();
void stop();
@@ -94,7 +112,6 @@ private:
void error(IOBluetoothDeviceInquiry *inq, IOReturn error) Q_DECL_OVERRIDE;
void deviceFound(IOBluetoothDeviceInquiry *inq, IOBluetoothDevice *device) Q_DECL_OVERRIDE;
- //
void LEinquiryFinished();
void LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error);
void LEnotSupported();
@@ -104,9 +121,8 @@ private:
void deviceFound(const QBluetoothDeviceInfo &newDeviceInfo);
void setError(IOReturn error, const QString &text = QString());
- void setError(QBluetoothDeviceDiscoveryAgent::Error, const QString &text = QString());
-
- void checkLETimeout();
+ void setError(QBluetoothDeviceDiscoveryAgent::Error,
+ const QString &text = QString());
QBluetoothDeviceDiscoveryAgent *q_ptr;
AgentState agentState;
@@ -121,56 +137,22 @@ private:
QBluetoothDeviceDiscoveryAgent::InquiryType inquiryType;
- typedef ObjCScopedPointer<DeviceInquiryObjC> DeviceInquiry;
+ using DeviceInquiry = ObjCScopedPointer<DeviceInquiryObjC>;
DeviceInquiry inquiry;
- typedef ObjCScopedPointer<LEDeviceInquiryObjC> LEDeviceInquiry;
+ using LEDeviceInquiry = ObjCScopedPointer<LEDeviceInquiryObjC>;
LEDeviceInquiry inquiryLE;
- typedef ObjCScopedPointer<IOBluetoothHostController> HostController;
+ using HostController = ObjCScopedPointer<IOBluetoothHostController>;
HostController hostController;
- typedef QList<QBluetoothDeviceInfo> DevicesList;
+ using DevicesList = QList<QBluetoothDeviceInfo>;
DevicesList discoveredDevices;
- QScopedPointer<OSXBluetooth::DDATimerHandler> timer;
+ int lowEnergySearchTimeout;
+ QBluetoothDeviceDiscoveryAgent::DiscoveryMethods requestedMethods;
};
-namespace OSXBluetooth {
-
-DDATimerHandler::DDATimerHandler(QBluetoothDeviceDiscoveryAgentPrivate *d)
- : owner(d)
-{
- Q_ASSERT_X(owner, Q_FUNC_INFO, "invalid pointer");
-
- timer.setSingleShot(false);
- connect(&timer, &QTimer::timeout, this, &DDATimerHandler::onTimer);
-}
-
-void DDATimerHandler::start(int msec)
-{
- Q_ASSERT_X(msec > 0, Q_FUNC_INFO, "invalid time interval");
- if (timer.isActive()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "timer is active";
- return;
- }
-
- timer.start(msec);
-}
-
-void DDATimerHandler::stop()
-{
- timer.stop();
-}
-
-void DDATimerHandler::onTimer()
-{
- Q_ASSERT(owner);
- owner->checkLETimeout();
-}
-
-}
-
QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &adapter,
QBluetoothDeviceDiscoveryAgent *q) :
q_ptr(q),
@@ -179,26 +161,22 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(con
startPending(false),
stopPending(false),
lastError(QBluetoothDeviceDiscoveryAgent::NoError),
- inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry)
+ inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry),
+ lowEnergySearchTimeout(OSXBluetooth::defaultLEScanTimeoutMS),
+ requestedMethods(QBluetoothDeviceDiscoveryAgent::ClassicMethod
+ | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)
{
+ registerQDeviceDiscoveryMetaType();
+
Q_ASSERT_X(q != Q_NULLPTR, Q_FUNC_INFO, "invalid q_ptr (null)");
HostController controller([[IOBluetoothHostController defaultController] retain]);
if (!controller || [controller powerState] != kBluetoothHCIPowerStateON) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "no default host "
- "controller or adapter is off";
- return;
- }
-
- DeviceInquiry newInquiry([[DeviceInquiryObjC alloc]initWithDelegate:this]);
- if (!newInquiry) { // Obj-C's way of "reporting errors":
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "initialize an inquiry";
+ qCCritical(QT_BT_OSX) << "no default host controller or adapter is off";
return;
}
hostController.reset(controller.take());
- inquiry.reset(newInquiry.take());
}
QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
@@ -208,7 +186,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) {
// Local variable to be retained ...
LEDeviceInquiryObjC *inq = inquiryLE.data();
- dispatch_async(leQueue, ^{
+ dispatch_sync(leQueue, ^{
[inq stop];
});
}
@@ -217,12 +195,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
bool QBluetoothDeviceDiscoveryAgentPrivate::isValid() const
{
- // isValid() - Qt does not use exceptions, but the ctor
- // can fail to initialize some important data-members
- // (and the error is probably not even related to Bluetooth at all)
- // - say, allocation error - this is what meant here by valid/invalid.
- return hostController && [hostController powerState] == kBluetoothHCIPowerStateON
- && inquiry;
+ return hostController && [hostController powerState] == kBluetoothHCIPowerStateON;
}
bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
@@ -236,23 +209,56 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
return agentState != NonActive;
}
-void QBluetoothDeviceDiscoveryAgentPrivate::start()
+void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
{
- Q_ASSERT_X(isValid(), Q_FUNC_INFO, "called on invalid device discovery agent");
- Q_ASSERT_X(!isActive(), Q_FUNC_INFO, "called on active device discovery agent");
- Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError,
- Q_FUNC_INFO, "called with an invalid Bluetooth adapter");
+ Q_ASSERT(isValid());
+ Q_ASSERT(!isActive());
+ Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError);
+ Q_ASSERT(methods & (QBluetoothDeviceDiscoveryAgent::ClassicMethod
+ | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod));
+
+ requestedMethods = methods;
if (stopPending) {
startPending = true;
return;
}
- agentState = ClassicScan;
+ // This function (re)starts the scan(s) from the scratch;
+ // starting from Classic if it's in 'methods' (or LE scan if not).
+ agentState = NonActive;
discoveredDevices.clear();
setError(QBluetoothDeviceDiscoveryAgent::NoError);
+ if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod)
+ return startClassic();
+
+ startLE();
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::startClassic()
+{
+ Q_ASSERT(isValid());
+ Q_ASSERT(!isActive());
+ Q_ASSERT(lastError == QBluetoothDeviceDiscoveryAgent::NoError);
+ Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod);
+ Q_ASSERT(agentState == NonActive);
+
+ if (!inquiry) {
+ // The first Classic scan for this DDA.
+ inquiry.reset([[DeviceInquiryObjC alloc]initWithDelegate:this]);
+ if (!inquiry) {
+ qCCritical(QT_BT_OSX) << "failed to initialize an Classic device inquiry";
+ setError(QBluetoothDeviceDiscoveryAgent::UnknownError,
+ QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED));
+ emit q_ptr->error(lastError);
+ return;
+ }
+ }
+
+ agentState = ClassicScan;
+
const IOReturn res = [inquiry start];
if (res != kIOReturnSuccess) {
setError(res, QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED));
@@ -263,13 +269,29 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start()
void QBluetoothDeviceDiscoveryAgentPrivate::startLE()
{
- Q_ASSERT_X(isValid(), Q_FUNC_INFO, "called on invalid device discovery agent");
- Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError,
- Q_FUNC_INFO, "called with an invalid Bluetooth adapter");
+ Q_ASSERT(isValid());
+ Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError);
+ Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
using namespace OSXBluetooth;
- inquiryLE.reset([[LEDeviceInquiryObjC alloc] init]);
+ QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier);
+ // Connections:
+ using ErrMemFunPtr = void (LECBManagerNotifier::*)(QBluetoothDeviceDiscoveryAgent::Error);
+ notifier->connect(notifier.data(), ErrMemFunPtr(&LECBManagerNotifier::CBManagerError),
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError);
+ notifier->connect(notifier.data(), &LECBManagerNotifier::LEnotSupported,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported);
+ notifier->connect(notifier.data(), &LECBManagerNotifier::discoveryFinished,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished);
+ using DeviceMemFunPtr = void (QBluetoothDeviceDiscoveryAgentPrivate::*)(const QBluetoothDeviceInfo &);
+ notifier->connect(notifier.data(), &LECBManagerNotifier::deviceDiscovered,
+ this, DeviceMemFunPtr(&QBluetoothDeviceDiscoveryAgentPrivate::deviceFound));
+
+ // Check queue and create scanner:
+ inquiryLE.reset([[LEDeviceInquiryObjC alloc] initWithNotifier:notifier.data()]);
+ if (inquiryLE)
+ notifier.take(); // Whatever happens next, inquiryLE is already the owner ...
dispatch_queue_t leQueue(qt_LE_queue());
if (!leQueue || !inquiryLE) {
@@ -277,18 +299,15 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startLE()
QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED_LE));
agentState = NonActive;
emit q_ptr->error(lastError);
+ return;
}
+ // Now start in on LE queue:
agentState = LEScan;
- // CoreBluetooth does not have a timeout. We start a timer here
- // and check if scan is active/finished/finished with error(s).
- timer.reset(new OSXBluetooth::DDATimerHandler(this));
- timer->start(2000);
-
// We need the local variable so that it's retained ...
LEDeviceInquiryObjC *inq = inquiryLE.data();
dispatch_async(leQueue, ^{
- [inq start];
+ [inq startWithTimeout:lowEnergySearchTimeout];
});
}
@@ -310,7 +329,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop()
if (agentState == ClassicScan) {
const IOReturn res = [inquiry stop];
if (res != kIOReturnSuccess) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to stop";
+ qCWarning(QT_BT_OSX) << "failed to stop";
startPending = prevStart;
stopPending = false;
setError(res, QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STOPPED));
@@ -321,7 +340,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop()
Q_ASSERT(leQueue);
// We need the local variable so that it's retained ...
LEDeviceInquiryObjC *inq = inquiryLE.data();
- dispatch_async(leQueue, ^{
+ dispatch_sync(leQueue, ^{
[inq stop];
});
// We consider LE scan to be stopped immediately and
@@ -346,13 +365,17 @@ void QBluetoothDeviceDiscoveryAgentPrivate::inquiryFinished(IOBluetoothDeviceInq
} else if (startPending) {
startPending = false;
stopPending = false;
- start();
+ start(requestedMethods);
} else {
// We can be here _only_ if a classic scan
- // finished in a normal way (not cancelled).
+ // finished in a normal way (not cancelled)
+ // and requestedMethods includes LowEnergyMethod.
// startLE() will take care of old devices
// not supporting Bluetooth 4.0.
- startLE();
+ if (requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)
+ startLE();
+ else
+ emit q_ptr->finished();
}
}
@@ -384,7 +407,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(IOBluetoothDeviceInquiry
// Let's collect some info about this device:
const QBluetoothAddress deviceAddress(OSXBluetooth::qt_address([device getAddress]));
if (deviceAddress.isNull()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid Bluetooth address";
+ qCWarning(QT_BT_OSX) << "invalid Bluetooth address";
return;
}
@@ -414,8 +437,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::setError(IOReturn error, const QStri
setError(QBluetoothDeviceDiscoveryAgent::UnknownError, text);
}
-void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error,
- const QString &text)
+void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error, const QString &text)
{
lastError = error;
@@ -442,53 +464,18 @@ void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAg
}
if (lastError != QBluetoothDeviceDiscoveryAgent::NoError)
- qCDebug(QT_BT_OSX) << "error set: "<<errorString;
-}
-
-void QBluetoothDeviceDiscoveryAgentPrivate::checkLETimeout()
-{
- Q_ASSERT_X(agentState == LEScan, Q_FUNC_INFO, "invalid agent state");
- Q_ASSERT_X(inquiryLE, Q_FUNC_INFO, "LE device inquiry is nil");
-
- using namespace OSXBluetooth;
-
- const LEInquiryState state([inquiryLE inquiryState]);
- if (state == InquiryStarting || state == InquiryActive)
- return; // Wait ...
-
- if (state == ErrorPoweredOff)
- return LEinquiryError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
-
- if (state == ErrorLENotSupported)
- return LEnotSupported();
-
- if (state == InquiryFinished) {
- // Process found devices if any ...
- const QList<QBluetoothDeviceInfo> leDevices([inquiryLE discoveredDevices]);
- foreach (const QBluetoothDeviceInfo &info, leDevices) {
- // We were cancelled on a previous device discovered signal ...
- if (agentState != LEScan)
- break;
- deviceFound(info);
- }
-
- if (agentState == LEScan)
- LEinquiryFinished();
- return;
- }
-
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected inquiry state in LE timeout";
- // Actually, this deserves an assert :)
+ qCDebug(QT_BT_OSX) << "error set:"<<errorString;
}
void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error)
{
- // At the moment the only error reported can be 'powered off' error, it happens
- // after the LE scan started (so we have LE support and this is a real PoweredOffError).
- Q_ASSERT(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError);
+ Q_ASSERT(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError
+ || error == QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
- timer->stop();
inquiryLE.reset();
+
+ startPending = false;
+ stopPending = false;
agentState = NonActive;
setError(error);
emit q_ptr->error(lastError);
@@ -496,9 +483,18 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDisco
void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported()
{
- // Not supported is not an error (we still have 'Classic').
qCDebug(QT_BT_OSX) << "no Bluetooth LE support";
- LEinquiryFinished();
+
+ if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) {
+ // Having both Classic | LE means this is not an error.
+ LEinquiryFinished();
+ } else {
+ // In the past this was never an error, that's why we have
+ // LEnotSupported as a special method. But now, since
+ // we can have separate Classic/LE scans, we have to report it
+ // as UnsupportedDiscoveryMethod.
+ LEinquiryError(QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
+ }
}
void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished()
@@ -506,7 +502,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished()
// The same logic as in inquiryFinished, but does not start LE scan.
agentState = NonActive;
inquiryLE.reset();
- timer->stop();
if (stopPending && !startPending) {
stopPending = false;
@@ -514,7 +509,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished()
} else if (startPending) {
startPending = false;
stopPending = false;
- start(); //Start from a classic scan again.
+ start(requestedMethods); //Start again.
} else {
emit q_ptr->finished();
}
@@ -574,7 +569,7 @@ QBluetoothDeviceDiscoveryAgent::InquiryType QBluetoothDeviceDiscoveryAgent::inqu
return d_ptr->inquiryType;
}
-void QBluetoothDeviceDiscoveryAgent::setInquiryType(QBluetoothDeviceDiscoveryAgent::InquiryType type)
+void QBluetoothDeviceDiscoveryAgent::setInquiryType(InquiryType type)
{
d_ptr->inquiryType = type;
}
@@ -584,12 +579,33 @@ QList<QBluetoothDeviceInfo> QBluetoothDeviceDiscoveryAgent::discoveredDevices()
return d_ptr->discoveredDevices;
}
+QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
+{
+ return ClassicMethod | LowEnergyMethod;
+}
+
void QBluetoothDeviceDiscoveryAgent::start()
{
+ start(supportedDiscoveryMethods());
+}
+
+void QBluetoothDeviceDiscoveryAgent::start(DiscoveryMethods methods)
+{
+ if (methods == NoMethod)
+ return;
+
+ if ((supportedDiscoveryMethods() & methods) != methods) {
+ d_ptr->lastError = UnsupportedDiscoveryMethod;
+ d_ptr->errorString = tr("One or more device discovery methods "
+ "are not supported on this platform");
+ emit error(d_ptr->lastError);
+ return;
+ }
+
if (d_ptr->lastError != InvalidBluetoothAdapterError) {
if (d_ptr->isValid()) {
if (!isActive())
- d_ptr->start();
+ d_ptr->start(methods);
} else {
// We previously failed to initialize d_ptr correctly:
// either some memory allocation problem or
@@ -626,4 +642,21 @@ QString QBluetoothDeviceDiscoveryAgent::errorString() const
return d_ptr->errorString;
}
+void QBluetoothDeviceDiscoveryAgent::setLowEnergyDiscoveryTimeout(int timeout)
+{
+ // cannot deliberately turn it off
+ if (timeout < 0) {
+ qCDebug(QT_BT_OSX) << "The Bluetooth Low Energy device discovery timeout cannot be negative.";
+ return;
+ }
+
+ d_ptr->lowEnergySearchTimeout = timeout;
+ return;
+}
+
+int QBluetoothDeviceDiscoveryAgent::lowEnergyDiscoveryTimeout() const
+{
+ return d_ptr->lowEnergySearchTimeout;
+}
+
QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_p.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_p.cpp
index 09076d6d..e3646db9 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_p.cpp
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_p.cpp
@@ -53,6 +53,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
QBluetoothDeviceDiscoveryAgent *parent)
: inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry),
lastError(QBluetoothDeviceDiscoveryAgent::NoError),
+ lowEnergySearchTimeout(-1),
q_ptr(parent)
{
Q_UNUSED(deviceAdapter);
@@ -68,7 +69,12 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
return false;
}
-void QBluetoothDeviceDiscoveryAgentPrivate::start()
+QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
+{
+ return QBluetoothDeviceDiscoveryAgent::NoMethod;
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods)
{
Q_Q(QBluetoothDeviceDiscoveryAgent);
lastError = QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError;
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h
index f49ff8b7..de8006be 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h
@@ -78,10 +78,14 @@ class QDBusVariant;
QT_END_NAMESPACE
#endif
+#ifdef QT_WINRT_BLUETOOTH
+class QWinRTBluetoothDeviceDiscoveryWorker;
+#endif
+
QT_BEGIN_NAMESPACE
class QBluetoothDeviceDiscoveryAgentPrivate
-#if defined(QT_ANDROID_BLUETOOTH)
+#if defined(QT_ANDROID_BLUETOOTH) || defined(QT_WINRT_BLUETOOTH)
: public QObject
{
Q_OBJECT
@@ -95,7 +99,7 @@ public:
QBluetoothDeviceDiscoveryAgent *parent);
~QBluetoothDeviceDiscoveryAgentPrivate();
- void start();
+ void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods);
void stop();
bool isActive() const;
@@ -155,6 +159,20 @@ private:
QTimer extendedDiscoveryTimer;
#endif
+#ifdef QT_WINRT_BLUETOOTH
+private slots:
+ void registerDevice(const QBluetoothDeviceInfo &info);
+ void onScanFinished();
+ void onScanCanceled();
+
+private:
+ void disconnectAndClearWorker();
+ QPointer<QWinRTBluetoothDeviceDiscoveryWorker> worker;
+ QTimer *leScanTimer;
+#endif
+
+ int lowEnergySearchTimeout;
+ QBluetoothDeviceDiscoveryAgent::DiscoveryMethods requestedMethods;
QBluetoothDeviceDiscoveryAgent *q_ptr;
};
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp
new file mode 100644
index 00000000..7813bfdc
--- /dev/null
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp
@@ -0,0 +1,647 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbluetoothdevicediscoveryagent.h"
+#include "qbluetoothdevicediscoveryagent_p.h"
+#include "qbluetoothaddress.h"
+#include "qbluetoothuuid.h"
+#include "qfunctions_winrt.h"
+
+#include <QtCore/QLoggingCategory>
+#include <QtCore/private/qeventdispatcher_winrt_p.h>
+
+#include <wrl.h>
+#include <windows.devices.enumeration.h>
+#include <windows.devices.bluetooth.h>
+#include <windows.foundation.collections.h>
+#include <windows.storage.streams.h>
+
+#ifndef Q_OS_WINPHONE
+#include <windows.devices.bluetooth.advertisement.h>
+
+using namespace ABI::Windows::Devices::Bluetooth::Advertisement;
+#endif // !Q_OS_WINPHONE
+
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::Foundation::Collections;
+using namespace ABI::Windows::Devices;
+using namespace ABI::Windows::Devices::Bluetooth;
+using namespace ABI::Windows::Devices::Enumeration;
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT)
+
+#define WARN_AND_RETURN_IF_FAILED(msg, ret) \
+ if (FAILED(hr)) { \
+ qCWarning(QT_BT_WINRT) << msg; \
+ ret; \
+ }
+
+class QWinRTBluetoothDeviceDiscoveryWorker : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QWinRTBluetoothDeviceDiscoveryWorker(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods);
+ ~QWinRTBluetoothDeviceDiscoveryWorker();
+ void start();
+ void stop();
+
+private:
+ void startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode);
+ void onDeviceDiscoveryFinished(IAsyncOperation<DeviceInformationCollection *> *op,
+ QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode);
+ void gatherDeviceInformation(IDeviceInformation *deviceInfo,
+ QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode);
+ void gatherMultipleDeviceInformation(IVectorView<DeviceInformation *> *devices,
+ QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode);
+ void setupLEDeviceWatcher();
+ void classicBluetoothInfoFromDeviceIdAsync(HSTRING deviceId);
+ void leBluetoothInfoFromDeviceIdAsync(HSTRING deviceId);
+ void leBluetoothInfoFromAddressAsync(quint64 address);
+ HRESULT onPairedClassicBluetoothDeviceFoundAsync(IAsyncOperation<BluetoothDevice *> *op, AsyncStatus status );
+ HRESULT onPairedBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status);
+ HRESULT onBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status);
+ enum PairingCheck {
+ CheckForPairing,
+ OmitPairingCheck
+ };
+ HRESULT onBluetoothLEDeviceFound(ComPtr<IBluetoothLEDevice> device, PairingCheck pairingCheck = CheckForPairing);
+
+public slots:
+ void handleLeTimeout();
+
+Q_SIGNALS:
+ void deviceFound(const QBluetoothDeviceInfo &info);
+ void scanFinished();
+ void scanCanceled();
+
+public:
+ quint8 requestedModes;
+
+private:
+#ifndef Q_OS_WINPHONE
+ ComPtr<IBluetoothLEAdvertisementWatcher> m_leWatcher;
+ EventRegistrationToken m_leDeviceAddedToken;
+ QVector<quint64> m_foundLEDevices;
+#endif // !Q_OS_WINPHONE
+ int m_pendingPairedDevices;
+
+ ComPtr<IBluetoothDeviceStatics> m_deviceStatics;
+ ComPtr<IBluetoothLEDeviceStatics> m_leDeviceStatics;
+};
+
+QWinRTBluetoothDeviceDiscoveryWorker::QWinRTBluetoothDeviceDiscoveryWorker(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
+ : requestedModes(methods)
+ , m_pendingPairedDevices(0)
+{
+ qRegisterMetaType<QBluetoothDeviceInfo>();
+
+ HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothDevice).Get(), &m_deviceStatics);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &m_leDeviceStatics);
+ Q_ASSERT_SUCCEEDED(hr);
+}
+
+QWinRTBluetoothDeviceDiscoveryWorker::~QWinRTBluetoothDeviceDiscoveryWorker()
+{
+ stop();
+}
+
+void QWinRTBluetoothDeviceDiscoveryWorker::start()
+{
+ QEventDispatcherWinRT::runOnXamlThread([this]() {
+ if (requestedModes & QBluetoothDeviceDiscoveryAgent::ClassicMethod)
+ startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::ClassicMethod);
+
+ if (requestedModes & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) {
+ startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
+ setupLEDeviceWatcher();
+ }
+ return S_OK;
+ });
+
+ qCDebug(QT_BT_WINRT) << "Worker started";
+}
+
+void QWinRTBluetoothDeviceDiscoveryWorker::stop()
+{
+#ifndef Q_OS_WINPHONE
+ if (m_leWatcher) {
+ HRESULT hr = m_leWatcher->Stop();
+ Q_ASSERT_SUCCEEDED(hr);
+ if (m_leDeviceAddedToken.value) {
+ hr = m_leWatcher->remove_Received(m_leDeviceAddedToken);
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+ }
+#endif // !Q_OS_WINPHONE
+}
+
+void QWinRTBluetoothDeviceDiscoveryWorker::startDeviceDiscovery(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode)
+{
+ HString deviceSelector;
+ ComPtr<IDeviceInformationStatics> deviceInformationStatics;
+ HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get(), &deviceInformationStatics);
+ WARN_AND_RETURN_IF_FAILED("Could not obtain device information statics", return);
+ if (mode == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)
+ m_leDeviceStatics->GetDeviceSelector(deviceSelector.GetAddressOf());
+ else
+ m_deviceStatics->GetDeviceSelector(deviceSelector.GetAddressOf());
+ ComPtr<IAsyncOperation<DeviceInformationCollection *>> op;
+ hr = deviceInformationStatics->FindAllAsyncAqsFilter(deviceSelector.Get(), &op);
+ WARN_AND_RETURN_IF_FAILED("Could not start bluetooth device discovery operation", return);
+ hr = op->put_Completed(
+ Callback<IAsyncOperationCompletedHandler<DeviceInformationCollection *>>([this, mode](IAsyncOperation<DeviceInformationCollection *> *op, AsyncStatus) {
+ onDeviceDiscoveryFinished(op, mode);
+ return S_OK;
+ }).Get());
+ WARN_AND_RETURN_IF_FAILED("Could not add callback to bluetooth device discovery operation", return);
+}
+
+void QWinRTBluetoothDeviceDiscoveryWorker::onDeviceDiscoveryFinished(IAsyncOperation<DeviceInformationCollection *> *op, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode)
+{
+ qCDebug(QT_BT_WINRT) << (mode == QBluetoothDeviceDiscoveryAgent::ClassicMethod ? "BT" : "BTLE")
+ << " scan completed";
+ ComPtr<IVectorView<DeviceInformation *>> devices;
+ HRESULT hr;
+ hr = op->GetResults(&devices);
+ Q_ASSERT_SUCCEEDED(hr);
+ gatherMultipleDeviceInformation(devices.Get(), mode);
+}
+
+void QWinRTBluetoothDeviceDiscoveryWorker::gatherDeviceInformation(IDeviceInformation *deviceInfo, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode)
+{
+ HString deviceId;
+ HRESULT hr;
+ hr = deviceInfo->get_Id(deviceId.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ if (mode == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)
+ leBluetoothInfoFromDeviceIdAsync(deviceId.Get());
+ else
+ classicBluetoothInfoFromDeviceIdAsync(deviceId.Get());
+}
+
+void QWinRTBluetoothDeviceDiscoveryWorker::gatherMultipleDeviceInformation(IVectorView<DeviceInformation *> *devices, QBluetoothDeviceDiscoveryAgent::DiscoveryMethod mode)
+{
+ quint32 deviceCount;
+ HRESULT hr = devices->get_Size(&deviceCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ m_pendingPairedDevices += deviceCount;
+ for (quint32 i = 0; i < deviceCount; ++i) {
+ ComPtr<IDeviceInformation> device;
+ hr = devices->GetAt(i, &device);
+ Q_ASSERT_SUCCEEDED(hr);
+ gatherDeviceInformation(device.Get(), mode);
+ }
+}
+
+void QWinRTBluetoothDeviceDiscoveryWorker::setupLEDeviceWatcher()
+{
+#ifndef Q_OS_WINPHONE
+ HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_Advertisement_BluetoothLEAdvertisementWatcher).Get(), &m_leWatcher);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = m_leWatcher->add_Received(Callback<ITypedEventHandler<BluetoothLEAdvertisementWatcher *, BluetoothLEAdvertisementReceivedEventArgs *>>([this](IBluetoothLEAdvertisementWatcher *, IBluetoothLEAdvertisementReceivedEventArgs *args) {
+ quint64 address;
+ HRESULT hr;
+ hr = args->get_BluetoothAddress(&address);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (m_foundLEDevices.contains(address))
+ return S_OK;
+
+ m_foundLEDevices.append(address);
+ leBluetoothInfoFromAddressAsync(address);
+ return S_OK;
+ }).Get(), &m_leDeviceAddedToken);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = m_leWatcher->Start();
+ Q_ASSERT_SUCCEEDED(hr);
+#endif // !Q_OS_WINPHONE
+}
+
+void QWinRTBluetoothDeviceDiscoveryWorker::handleLeTimeout()
+{
+ if (m_pendingPairedDevices == 0)
+ emit scanFinished();
+ else
+ emit scanCanceled();
+ deleteLater();
+}
+
+// "deviceFound" will be emitted at the end of the deviceFromIdOperation callback
+void QWinRTBluetoothDeviceDiscoveryWorker::classicBluetoothInfoFromDeviceIdAsync(HSTRING deviceId)
+{
+ HRESULT hr;
+ hr = QEventDispatcherWinRT::runOnXamlThread([deviceId, this]() {
+ ComPtr<IAsyncOperation<BluetoothDevice *>> deviceFromIdOperation;
+ // on Windows 10 FromIdAsync might ask for device permission. We cannot wait here but have to handle that asynchronously
+ HRESULT hr = m_deviceStatics->FromIdAsync(deviceId, &deviceFromIdOperation);
+ if (FAILED(hr)) {
+ --m_pendingPairedDevices;
+ qCWarning(QT_BT_WINRT) << "Could not obtain bluetooth device from id";
+ return S_OK;
+ }
+
+ hr = deviceFromIdOperation->put_Completed(Callback<IAsyncOperationCompletedHandler<BluetoothDevice *>>
+ (this, &QWinRTBluetoothDeviceDiscoveryWorker::onPairedClassicBluetoothDeviceFoundAsync).Get());
+ if (FAILED(hr)) {
+ --m_pendingPairedDevices;
+ qCWarning(QT_BT_WINRT) << "Could not register device found callback";
+ return S_OK;
+ }
+ return S_OK;
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+}
+
+// "deviceFound" will be emitted at the end of the deviceFromIdOperation callback
+void QWinRTBluetoothDeviceDiscoveryWorker::leBluetoothInfoFromDeviceIdAsync(HSTRING deviceId)
+{
+ HRESULT hr;
+ hr = QEventDispatcherWinRT::runOnXamlThread([deviceId, this]() {
+ ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromIdOperation;
+ // on Windows 10 FromIdAsync might ask for device permission. We cannot wait here but have to handle that asynchronously
+ HRESULT hr = m_leDeviceStatics->FromIdAsync(deviceId, &deviceFromIdOperation);
+ if (FAILED(hr)) {
+ --m_pendingPairedDevices;
+ qCWarning(QT_BT_WINRT) << "Could not obtain bluetooth device from id";
+ return S_OK;
+ }
+
+ hr = deviceFromIdOperation->put_Completed(Callback<IAsyncOperationCompletedHandler<BluetoothLEDevice *>>
+ (this, &QWinRTBluetoothDeviceDiscoveryWorker::onPairedBluetoothLEDeviceFoundAsync).Get());
+ if (FAILED(hr)) {
+ --m_pendingPairedDevices;
+ qCWarning(QT_BT_WINRT) << "Could not register device found callback";
+ return S_OK;
+ }
+ return S_OK;
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+}
+
+// "deviceFound" will be emitted at the end of the deviceFromAdressOperation callback
+void QWinRTBluetoothDeviceDiscoveryWorker::leBluetoothInfoFromAddressAsync(quint64 address)
+{
+ HRESULT hr;
+ hr = QEventDispatcherWinRT::runOnXamlThread([address, this]() {
+ ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromAddressOperation;
+ // on Windows 10 FromBluetoothAddressAsync might ask for device permission. We cannot wait
+ // here but have to handle that asynchronously
+ HRESULT hr = m_leDeviceStatics->FromBluetoothAddressAsync(address, &deviceFromAddressOperation);
+ if (FAILED(hr)) {
+ qCWarning(QT_BT_WINRT) << "Could not obtain bluetooth device from address";
+ return S_OK;
+ }
+
+ hr = deviceFromAddressOperation->put_Completed(Callback<IAsyncOperationCompletedHandler<BluetoothLEDevice *>>
+ (this, &QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFoundAsync).Get());
+ if (FAILED(hr)) {
+ qCWarning(QT_BT_WINRT) << "Could not register device found callback";
+ return S_OK;
+ }
+ return S_OK;
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+}
+
+HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onPairedClassicBluetoothDeviceFoundAsync(IAsyncOperation<BluetoothDevice *> *op, AsyncStatus status)
+{
+ --m_pendingPairedDevices;
+ if (status != AsyncStatus::Completed)
+ return S_OK;
+
+ ComPtr<IBluetoothDevice> device;
+ HRESULT hr = op->GetResults(&device);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ if (!device)
+ return S_OK;
+
+ UINT64 address;
+ HString name;
+ ComPtr<IBluetoothClassOfDevice> classOfDevice;
+ UINT32 classOfDeviceInt;
+ hr = device->get_BluetoothAddress(&address);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = device->get_Name(name.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ const QString btName = QString::fromWCharArray(WindowsGetStringRawBuffer(name.Get(), nullptr));
+ hr = device->get_ClassOfDevice(&classOfDevice);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = classOfDevice->get_RawValue(&classOfDeviceInt);
+ Q_ASSERT_SUCCEEDED(hr);
+ IVectorView <Rfcomm::RfcommDeviceService *> *deviceServices;
+ hr = device->get_RfcommServices(&deviceServices);
+ Q_ASSERT_SUCCEEDED(hr);
+ uint serviceCount;
+ hr = deviceServices->get_Size(&serviceCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ QList<QBluetoothUuid> uuids;
+ for (uint i = 0; i < serviceCount; ++i) {
+ ComPtr<Rfcomm::IRfcommDeviceService> service;
+ hr = deviceServices->GetAt(i, &service);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<Rfcomm::IRfcommServiceId> id;
+ hr = service->get_ServiceId(&id);
+ Q_ASSERT_SUCCEEDED(hr);
+ GUID uuid;
+ hr = id->get_Uuid(&uuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ uuids.append(QBluetoothUuid(uuid));
+ }
+
+ qCDebug(QT_BT_WINRT) << "Discovered BT device: " << QString::number(address) << btName
+ << "Num UUIDs" << uuids.count();
+
+ QBluetoothDeviceInfo info(QBluetoothAddress(address), btName, classOfDeviceInt);
+ info.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration);
+ info.setServiceUuids(uuids, QBluetoothDeviceInfo::DataIncomplete);
+ info.setCached(true);
+
+ QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection,
+ Q_ARG(QBluetoothDeviceInfo, info));
+ return S_OK;
+}
+
+HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onPairedBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status)
+{
+ --m_pendingPairedDevices;
+ if (status != AsyncStatus::Completed)
+ return S_OK;
+
+ ComPtr<IBluetoothLEDevice> device;
+ HRESULT hr;
+ hr = op->GetResults(&device);
+ Q_ASSERT_SUCCEEDED(hr);
+ return onBluetoothLEDeviceFound(device, OmitPairingCheck);
+}
+
+HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status)
+{
+ if (status != AsyncStatus::Completed)
+ return S_OK;
+
+ ComPtr<IBluetoothLEDevice> device;
+ HRESULT hr;
+ hr = op->GetResults(&device);
+ Q_ASSERT_SUCCEEDED(hr);
+ return onBluetoothLEDeviceFound(device, PairingCheck::CheckForPairing);
+}
+
+HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFound(ComPtr<IBluetoothLEDevice> device, PairingCheck pairingCheck)
+{
+ if (!device) {
+ qCDebug(QT_BT_WINRT) << "onBluetoothLEDeviceFound: No device given";
+ return S_OK;
+ }
+
+ if (pairingCheck == CheckForPairing) {
+#ifndef Q_OS_WINPHONE
+ ComPtr<IBluetoothLEDevice2> device2;
+ HRESULT hr = device.As(&device2);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IDeviceInformation> deviceInfo;
+ hr = device2->get_DeviceInformation(&deviceInfo);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IDeviceInformation2> deviceInfo2;
+ hr = deviceInfo.As(&deviceInfo2);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IDeviceInformationPairing> pairing;
+ hr = deviceInfo2->get_Pairing(&pairing);
+ Q_ASSERT_SUCCEEDED(hr);
+ boolean isPaired;
+ hr = pairing->get_IsPaired(&isPaired);
+ Q_ASSERT_SUCCEEDED(hr);
+ // We need a paired device in order to be able to obtain its information
+ if (!isPaired) {
+ ComPtr<IAsyncOperation<DevicePairingResult *>> pairingOp;
+ hr = pairing.Get()->PairAsync(&pairingOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ pairingOp.Get()->put_Completed(
+ Callback<IAsyncOperationCompletedHandler<DevicePairingResult *>>([device, this](IAsyncOperation<DevicePairingResult *> *op, AsyncStatus status) {
+ if (status != AsyncStatus::Completed) {
+ qCDebug(QT_BT_WINRT) << "Could not pair device";
+ return S_OK;
+ }
+
+ ComPtr<IDevicePairingResult> result;
+ op->GetResults(&result);
+
+ DevicePairingResultStatus pairingStatus;
+ result.Get()->get_Status(&pairingStatus);
+
+ if (pairingStatus != DevicePairingResultStatus_Paired) {
+ qCDebug(QT_BT_WINRT) << "Could not pair device";
+ return S_OK;
+ }
+
+ onBluetoothLEDeviceFound(device, OmitPairingCheck);
+ return S_OK;
+ }).Get());
+ return S_OK;
+ }
+#else // !Q_OS_WINPHONE
+ Q_ASSERT(false);
+#endif // Q_OS_WINPHONE
+ }
+
+ UINT64 address;
+ HString name;
+ HRESULT hr = device->get_BluetoothAddress(&address);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = device->get_Name(name.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ const QString btName = QString::fromWCharArray(WindowsGetStringRawBuffer(name.Get(), nullptr));
+ IVectorView <GenericAttributeProfile::GattDeviceService *> *deviceServices;
+ hr = device->get_GattServices(&deviceServices);
+ Q_ASSERT_SUCCEEDED(hr);
+ uint serviceCount;
+ hr = deviceServices->get_Size(&serviceCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ QList<QBluetoothUuid> uuids;
+ for (uint i = 0; i < serviceCount; ++i) {
+ ComPtr<GenericAttributeProfile::IGattDeviceService> service;
+ hr = deviceServices->GetAt(i, &service);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<Rfcomm::IRfcommServiceId> id;
+ GUID uuid;
+ hr = service->get_Uuid(&uuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ uuids.append(QBluetoothUuid(uuid));
+ }
+
+ qCDebug(QT_BT_WINRT) << "Discovered BTLE device: " << QString::number(address) << btName
+ << "Num UUIDs" << uuids.count();
+
+ QBluetoothDeviceInfo info(QBluetoothAddress(address), btName, 0);
+ info.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
+ info.setServiceUuids(uuids, QBluetoothDeviceInfo::DataIncomplete);
+ info.setCached(true);
+
+ QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection,
+ Q_ARG(QBluetoothDeviceInfo, info));
+ return S_OK;
+}
+
+QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
+ const QBluetoothAddress &deviceAdapter,
+ QBluetoothDeviceDiscoveryAgent *parent)
+
+ : inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry),
+ lastError(QBluetoothDeviceDiscoveryAgent::NoError),
+ lowEnergySearchTimeout(25000),
+ q_ptr(parent),
+ leScanTimer(0)
+{
+ Q_UNUSED(deviceAdapter);
+}
+
+QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
+{
+ disconnectAndClearWorker();
+}
+
+bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
+{
+ return worker;
+}
+
+QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
+{
+ return (ClassicMethod | LowEnergyMethod);
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
+{
+ if (worker)
+ return;
+
+ worker = new QWinRTBluetoothDeviceDiscoveryWorker(methods);
+ discoveredDevices.clear();
+ connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::deviceFound,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice);
+ connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished);
+ connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanCanceled,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanCanceled);
+ worker->start();
+
+ if (lowEnergySearchTimeout > 0 && methods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) { // otherwise no timeout and stop() required
+ if (!leScanTimer) {
+ leScanTimer = new QTimer(this);
+ leScanTimer->setSingleShot(true);
+ }
+ connect(leScanTimer, &QTimer::timeout,
+ worker, &QWinRTBluetoothDeviceDiscoveryWorker::handleLeTimeout);
+ leScanTimer->setInterval(lowEnergySearchTimeout);
+ leScanTimer->start();
+ }
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::stop()
+{
+ Q_Q(QBluetoothDeviceDiscoveryAgent);
+ if (worker) {
+ worker->stop();
+ disconnectAndClearWorker();
+ emit q->canceled();
+ }
+ if (leScanTimer) {
+ leScanTimer->stop();
+ worker->deleteLater();
+ }
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::registerDevice(const QBluetoothDeviceInfo &info)
+{
+ Q_Q(QBluetoothDeviceDiscoveryAgent);
+
+ for (QList<QBluetoothDeviceInfo>::iterator iter = discoveredDevices.begin();
+ iter != discoveredDevices.end(); ++iter) {
+ if (iter->address() == info.address()) {
+ qCDebug(QT_BT_WINRT) << "Updating device" << iter->name() << iter->address();
+ // merge service uuids
+ QList<QBluetoothUuid> uuids = iter->serviceUuids();
+ uuids.append(info.serviceUuids());
+ const QSet<QBluetoothUuid> uuidSet = uuids.toSet();
+ if (iter->serviceUuids().count() != uuidSet.count())
+ iter->setServiceUuids(uuidSet.toList(), QBluetoothDeviceInfo::DataIncomplete);
+ if (iter->coreConfigurations() != info.coreConfigurations())
+ iter->setCoreConfigurations(QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration);
+ return;
+ }
+ }
+
+ discoveredDevices << info;
+ emit q->deviceDiscovered(info);
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished()
+{
+ Q_Q(QBluetoothDeviceDiscoveryAgent);
+ disconnectAndClearWorker();
+ emit q->finished();
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::onScanCanceled()
+{
+ Q_Q(QBluetoothDeviceDiscoveryAgent);
+ disconnectAndClearWorker();
+ emit q->canceled();
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::disconnectAndClearWorker()
+{
+ Q_Q(QBluetoothDeviceDiscoveryAgent);
+ if (!worker)
+ return;
+
+ disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanCanceled,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanCanceled);
+ disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished,
+ this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished);
+ disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::deviceFound,
+ q, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered);
+ if (leScanTimer) {
+ disconnect(leScanTimer, &QTimer::timeout,
+ worker, &QWinRTBluetoothDeviceDiscoveryWorker::handleLeTimeout);
+ }
+ worker.clear();
+}
+
+QT_END_NAMESPACE
+
+#include <qbluetoothdevicediscoveryagent_winrt.moc>
diff --git a/src/bluetooth/qbluetoothdevicediscoverytimer_osx_p.h b/src/bluetooth/qbluetoothdevicediscoverytimer_osx_p.h
deleted file mode 100644
index 88906ffd..00000000
--- a/src/bluetooth/qbluetoothdevicediscoverytimer_osx_p.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/****************************************************************************
-**
-** 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 QBLUETOOTHDEVICEDISCOVERYTIMER_OSX_P_H
-#define QBLUETOOTHDEVICEDISCOVERYTIMER_OSX_P_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include <QtCore/qglobal.h>
-#include <QtCore/qtimer.h>
-
-QT_BEGIN_NAMESPACE
-
-class QBluetoothDeviceDiscoveryAgentPrivate;
-
-namespace OSXBluetooth {
-
-class DDATimerHandler : public QObject
-{
- Q_OBJECT
-
-public:
- DDATimerHandler(QBluetoothDeviceDiscoveryAgentPrivate *d);
-
- void start(int msec);
- void stop();
-
-private slots:
- void onTimer();
-
-private:
- QTimer timer;
- QBluetoothDeviceDiscoveryAgentPrivate *owner;
-};
-
-}
-
-QT_END_NAMESPACE
-
-#endif // QBLUETOOTHDEVICEDISCOVERYTIMER_OSX_P_H
diff --git a/src/bluetooth/qbluetoothlocaldevice_android.cpp b/src/bluetooth/qbluetoothlocaldevice_android.cpp
index 72162fd4..f36e184c 100644
--- a/src/bluetooth/qbluetoothlocaldevice_android.cpp
+++ b/src/bluetooth/qbluetoothlocaldevice_android.cpp
@@ -87,18 +87,36 @@ QAndroidJniObject *QBluetoothLocalDevicePrivate::adapter()
return obj;
}
-void QBluetoothLocalDevicePrivate::initialize(const QBluetoothAddress &address)
+static QAndroidJniObject getDefaultAdapter()
{
- QAndroidJniEnvironment env;
-
QAndroidJniObject adapter = QAndroidJniObject::callStaticObjectMethod(
"android/bluetooth/BluetoothAdapter", "getDefaultAdapter",
"()Landroid/bluetooth/BluetoothAdapter;");
if (!adapter.isValid()) {
+ QAndroidJniEnvironment env;
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
+
+ // workaround stupid bt implementations where first call of BluetoothAdapter.getDefaultAdapter() always fails
+ adapter = QAndroidJniObject::callStaticObjectMethod(
+ "android/bluetooth/BluetoothAdapter", "getDefaultAdapter",
+ "()Landroid/bluetooth/BluetoothAdapter;");
+ if (!adapter.isValid()) {
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+ }
+ }
+ return adapter;
+}
+
+void QBluetoothLocalDevicePrivate::initialize(const QBluetoothAddress &address)
+{
+ QAndroidJniObject adapter = getDefaultAdapter();
+ if (!adapter.isValid()) {
qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth";
return;
}
@@ -304,33 +322,7 @@ QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices()
// Android only supports max of one device (so far)
QList<QBluetoothHostInfo> localDevices;
- QAndroidJniEnvironment env;
- jclass btAdapterClass = env->FindClass("android/bluetooth/BluetoothAdapter");
- if (btAdapterClass == NULL) {
- qCWarning(QT_BT_ANDROID)
- << "Native registration unable to find class android/bluetooth/BluetoothAdapter";
- return localDevices;
- }
-
- jmethodID getDefaultAdapterID
- = env->GetStaticMethodID(btAdapterClass, "getDefaultAdapter",
- "()Landroid/bluetooth/BluetoothAdapter;");
- if (getDefaultAdapterID == NULL) {
- qCWarning(QT_BT_ANDROID)
- << "Native registration unable to get method ID: " \
- "getDefaultAdapter of android/bluetooth/BluetoothAdapter";
- env->DeleteLocalRef(btAdapterClass);
- return localDevices;
- }
-
- jobject btAdapterObject = env->CallStaticObjectMethod(btAdapterClass, getDefaultAdapterID);
- if (btAdapterObject == NULL) {
- qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth";
- env->DeleteLocalRef(btAdapterClass);
- return localDevices;
- }
-
- QAndroidJniObject o(btAdapterObject);
+ QAndroidJniObject o = getDefaultAdapter();
if (o.isValid()) {
QBluetoothHostInfo info;
info.setName(o.callObjectMethod("getName", "()Ljava/lang/String;").toString());
@@ -338,10 +330,6 @@ QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices()
"()Ljava/lang/String;").toString()));
localDevices.append(info);
}
-
- env->DeleteLocalRef(btAdapterObject);
- env->DeleteLocalRef(btAdapterClass);
-
return localDevices;
}
diff --git a/src/bluetooth/qbluetoothlocaldevice_osx.mm b/src/bluetooth/qbluetoothlocaldevice_osx.mm
index c005e2ea..45fa310a 100644
--- a/src/bluetooth/qbluetoothlocaldevice_osx.mm
+++ b/src/bluetooth/qbluetoothlocaldevice_osx.mm
@@ -118,29 +118,25 @@ QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate(QBluetoothLocalDevice
HostController defaultController([[IOBluetoothHostController defaultController] retain]);
if (!defaultController) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "init a host controller object";
+ qCCritical(QT_BT_OSX) << "failed to init a host controller object";
return;
}
if (!address.isNull()) {
NSString *const hciAddress = [defaultController addressAsString];
if (!hciAddress) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "obtain an address";
+ qCCritical(QT_BT_OSX) << "failed to obtain an address";
return;
}
BluetoothDeviceAddress iobtAddress = {};
if (IOBluetoothNSStringToDeviceAddress(hciAddress, &iobtAddress) != kIOReturnSuccess) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "invalid "
- "local device's address";
+ qCCritical(QT_BT_OSX) << "invalid local device's address";
return;
}
if (address != OSXBluetooth::qt_address(&iobtAddress)) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "invalid "
- "local device's address";
+ qCCritical(QT_BT_OSX) << "invalid local device's address";
return;
}
}
@@ -183,8 +179,7 @@ void QBluetoothLocalDevicePrivate::requestPairing(const QBluetoothAddress &addre
if ([device isPaired]) {
emitPairingFinished(address, pairing, true);
} else if ([pos.value() start] != kIOReturnSuccess) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "start a new pairing request";
+ qCCritical(QT_BT_OSX) << "failed to start a new pairing request";
emitError(QBluetoothLocalDevice::PairingError, true);
}
return;
@@ -195,8 +190,7 @@ void QBluetoothLocalDevicePrivate::requestPairing(const QBluetoothAddress &addre
// it'll just finish with success (skipping any intermediate steps).
PairingRequest newRequest([[ObjCPairingRequest alloc] initWithTarget:address delegate:this], false);
if (!newRequest) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "allocate a new pairing request";
+ qCCritical(QT_BT_OSX) << "failed to allocate a new pairing request";
emitError(QBluetoothLocalDevice::PairingError, true);
return;
}
@@ -205,8 +199,7 @@ void QBluetoothLocalDevicePrivate::requestPairing(const QBluetoothAddress &addre
const IOReturn result = [newRequest start];
if (result != kIOReturnSuccess) {
pairingRequests.erase(pos);
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "start a new pairing request";
+ qCCritical(QT_BT_OSX) << "failed to start a new pairing request";
emitError(QBluetoothLocalDevice::PairingError, true);
}
}
diff --git a/src/bluetooth/qbluetoothlocaldevice_p.cpp b/src/bluetooth/qbluetoothlocaldevice_p.cpp
index f266d64c..e9177f39 100644
--- a/src/bluetooth/qbluetoothlocaldevice_p.cpp
+++ b/src/bluetooth/qbluetoothlocaldevice_p.cpp
@@ -103,6 +103,9 @@ void QBluetoothLocalDevice::requestPairing(const QBluetoothAddress &address, Pai
{
Q_UNUSED(address);
Q_UNUSED(pairing);
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QBluetoothLocalDevice::Error,
+ QBluetoothLocalDevice::PairingError));
}
QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus(
diff --git a/src/bluetooth/qbluetoothserver_osx.mm b/src/bluetooth/qbluetoothserver_osx.mm
index 549a07cc..8896651d 100644
--- a/src/bluetooth/qbluetoothserver_osx.mm
+++ b/src/bluetooth/qbluetoothserver_osx.mm
@@ -95,7 +95,7 @@ QBluetoothServerPrivate::QBluetoothServerPrivate(QSInfo::Protocol type, QBluetoo
{
Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
if (serverType == QSInfo::UnknownProtocol)
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown protocol";
+ qCWarning(QT_BT_OSX) << "unknown protocol";
}
QBluetoothServerPrivate::~QBluetoothServerPrivate()
@@ -116,7 +116,7 @@ bool QBluetoothServerPrivate::startListener(quint16 realPort)
Q_ASSERT_X(realPort, Q_FUNC_INFO, "invalid port");
if (serverType == QSInfo::UnknownProtocol) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid protocol";
+ qCWarning(QT_BT_OSX) << "invalid protocol";
return false;
}
@@ -217,7 +217,7 @@ void QBluetoothServerPrivate::registerServer(QBluetoothServerPrivate *server, qu
Q_ASSERT_X(!psmIsBusy(port), Q_FUNC_INFO, "port is busy");
busyPSMs()[port] = server;
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "can not register a server "
+ qCWarning(QT_BT_OSX) << "can not register a server "
"with unknown protocol type";
}
}
@@ -234,7 +234,7 @@ QBluetoothServerPrivate *QBluetoothServerPrivate::registeredServer(quint16 port,
if (it != busyPSMs().end())
return it.value();
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid protocol";
+ qCWarning(QT_BT_OSX) << "invalid protocol";
}
return Q_NULLPTR;
@@ -251,17 +251,17 @@ void QBluetoothServerPrivate::unregisterServer(QBluetoothServerPrivate *server)
if (it != busyChannels().end()) {
busyChannels().erase(it);
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "server is not registered";
+ qCWarning(QT_BT_OSX) << "server is not registered";
}
} else if (type == QSInfo::L2capProtocol) {
ServerMapIterator it = busyPSMs().find(port);
if (it != busyPSMs().end()) {
busyPSMs().erase(it);
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "server is not registered";
+ qCWarning(QT_BT_OSX) << "server is not registered";
}
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid protocol";
+ qCWarning(QT_BT_OSX) << "invalid protocol";
}
}
@@ -292,16 +292,15 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port)
typedef QBluetoothServerPrivate::ObjCListener ObjCListener;
if (d_ptr->listener) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "already in listen mode, "
- "close server first";
+ qCWarning(QT_BT_OSX) << "already in listen mode, close server first";
return false;
}
const QBluetoothLocalDevice device(address);
if (!device.isValid()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "device does not support Bluetooth or "
+ qCWarning(QT_BT_OSX) << "device does not support Bluetooth or"
<< address.toString()
- << " is not a valid local adapter";
+ << "is not a valid local adapter";
d_ptr->lastError = UnknownError;
emit error(UnknownError);
return false;
@@ -309,7 +308,7 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port)
const QBluetoothLocalDevice::HostMode hostMode = device.hostMode();
if (hostMode == QBluetoothLocalDevice::HostPoweredOff) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "Bluetooth device is powered off";
+ qCWarning(QT_BT_OSX) << "Bluetooth device is powered off";
d_ptr->lastError = PoweredOffError;
emit error(PoweredOffError);
return false;
@@ -318,7 +317,7 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port)
const QSInfo::Protocol type = d_ptr->serverType;
if (type == QSInfo::UnknownProtocol) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid protocol";
+ qCWarning(QT_BT_OSX) << "invalid protocol";
d_ptr->lastError = UnsupportedProtocolError;
emit error(d_ptr->lastError);
return false;
@@ -332,14 +331,14 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port)
if (port) {
if (type == QSInfo::RfcommProtocol) {
if (d_ptr->channelIsBusy(port)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO <<"server port: "
- << port << "already registered";
+ qCWarning(QT_BT_OSX) << "server port:" << port
+ << "already registered";
d_ptr->lastError = ServiceAlreadyRegisteredError;
}
} else {
if (d_ptr->psmIsBusy(port)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "server port: "
- << port << "already registered";
+ qCWarning(QT_BT_OSX) << "server port:" << port
+ << "already registered";
d_ptr->lastError = ServiceAlreadyRegisteredError;
}
}
@@ -354,7 +353,7 @@ bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port)
}
if (!port) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "all ports are busy";
+ qCWarning(QT_BT_OSX) << "all ports are busy";
d_ptr->lastError = ServiceAlreadyRegisteredError;
emit error(d_ptr->lastError);
return false;
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm
index 5c4efca2..4a52b379 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm
@@ -335,7 +335,7 @@ void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryError(IOBluetoothDevice *
{
Q_UNUSED(device)
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "inquiry failed with IOKit code: " << int(errorCode);
+ qCWarning(QT_BT_OSX) << "inquiry failed with IOKit code:" << int(errorCode);
discoveredDevices.clear();
// TODO: find a better mapping from IOReturn to QBluetoothServiceDiscoveryAgent::Error.
diff --git a/src/bluetooth/qbluetoothserviceinfo_osx.mm b/src/bluetooth/qbluetoothserviceinfo_osx.mm
index d6aa7592..a25653ab 100644
--- a/src/bluetooth/qbluetoothserviceinfo_osx.mm
+++ b/src/bluetooth/qbluetoothserviceinfo_osx.mm
@@ -46,7 +46,6 @@
#include <QtCore/qloggingcategory.h>
#include <QtCore/qvariant.h>
-#include <QtCore/qsysinfo.h>
#include <QtCore/qglobal.h>
#include <QtCore/qmutex.h>
#include <QtCore/qmap.h>
@@ -56,44 +55,6 @@
QT_BEGIN_NAMESPACE
-namespace {
-
-// This is not in osxbtutility_p, since it's not required
-// in general and just fixes the problem with SDK < 10.9,
-// where we have to care about about IOBluetoothSDPServiceRecordRef.
-class ServiceRecordRefGuard
-{
-public:
- ServiceRecordRefGuard()
- : recordRef(Q_NULLPTR)
- {
- }
- explicit ServiceRecordRefGuard(IOBluetoothSDPServiceRecordRef r)
- : recordRef(r)
- {
- }
- ~ServiceRecordRefGuard()
- {
- if (recordRef) // Requires non-NULL pointers.
- CFRelease(recordRef);
- }
-
- void reset(IOBluetoothSDPServiceRecordRef r)
- {
- if (recordRef)
- CFRelease(recordRef);
- // Take the ownership:
- recordRef = r;
- }
-
-private:
- IOBluetoothSDPServiceRecordRef recordRef;
-
- Q_DISABLE_COPY(ServiceRecordRefGuard)
-};
-
-}
-
class QBluetoothServiceInfoPrivate
{
public:
@@ -144,48 +105,23 @@ bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress &loca
serviceDict(iobluetooth_service_dictionary(*q_ptr));
if (!serviceDict) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to create a service dictionary";
+ qCWarning(QT_BT_OSX) << "failed to create a service dictionary";
return false;
}
- ServiceRecordRefGuard refGuard;
SDPRecord newRecord;
-#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9)
- if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) {
- newRecord.reset([[IOBluetoothSDPServiceRecord
- publishedServiceRecordWithDictionary:serviceDict] retain]);
- } else {
-#else
- {
-#endif
- IOBluetoothSDPServiceRecordRef recordRef = Q_NULLPTR;
- // With ARC this will require a different cast?
- const IOReturn status = IOBluetoothAddServiceDict((CFDictionaryRef)serviceDict.data(), &recordRef);
- if (status != kIOReturnSuccess) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to register a service record";
- return false;
- }
-
- refGuard.reset(recordRef);
- newRecord.reset([[IOBluetoothSDPServiceRecord withSDPServiceRecordRef:recordRef] retain]);
- // It's weird, but ... it's not possible to release a record ref yet.
- }
+ newRecord.reset([[IOBluetoothSDPServiceRecord
+ publishedServiceRecordWithDictionary:serviceDict] retain]);
if (!newRecord) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to register a service record";
- // In case of SDK < 10.9 it's not possible to remove a service record ...
- // no way to obtain record handle yet.
+ qCWarning(QT_BT_OSX) << "failed to register a service record";
return false;
}
BluetoothSDPServiceRecordHandle newRecordHandle = 0;
if ([newRecord getServiceRecordHandle:&newRecordHandle] != kIOReturnSuccess) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to register a service record";
-#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9)
- if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9)
- [newRecord removeServiceRecord];
-#endif
- // With SDK < 10.9 there is no way to unregister at this point ...
+ qCWarning(QT_BT_OSX) << "failed to register a service record";
+ [newRecord removeServiceRecord];
return false;
}
@@ -211,17 +147,8 @@ bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress &loca
}
if (!configured) {
-#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9)
- if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) {
- [newRecord removeServiceRecord];
- } else {
-#else
- {// Just to balance braces ...
-#endif
- IOBluetoothRemoveServiceWithRecordHandle(newRecordHandle);
- }
-
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to register a service record";
+ [newRecord removeServiceRecord];
+ qCWarning(QT_BT_OSX) << "failed to register a service record";
return false;
}
@@ -247,16 +174,7 @@ bool QBluetoothServiceInfoPrivate::unregisterService()
Q_ASSERT_X(serviceRecord, Q_FUNC_INFO, "service registered, but serviceRecord is nil");
-#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9)
- if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_9) {
- [serviceRecord removeServiceRecord];
- } else {
-#else
- {
-#endif
- // Assert on newRecordHandle? Is 0 a valid/invalid handle?
- IOBluetoothRemoveServiceWithRecordHandle(serviceRecordHandle);
- }
+ [serviceRecord removeServiceRecord];
serviceRecord.reset(nil);
diff --git a/src/bluetooth/qbluetoothsocket_android.cpp b/src/bluetooth/qbluetoothsocket_android.cpp
index ab8cd876..56d4f77b 100644
--- a/src/bluetooth/qbluetoothsocket_android.cpp
+++ b/src/bluetooth/qbluetoothsocket_android.cpp
@@ -252,15 +252,15 @@ bool QBluetoothSocketPrivate::fallBackConnect(QAndroidJniObject uuid, int channe
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
- }
-
- if (socketChannel
- == remoteDevice.getStaticField<jint>("android/bluetooth/BluetoothDevice", "ERROR")
- || socketChannel == -1) {
- qCWarning(QT_BT_ANDROID) << "Cannot determine RFCOMM service channel.";
} else {
- qCWarning(QT_BT_ANDROID) << "Using found rfcomm channel" << socketChannel;
- channel = socketChannel;
+ if (socketChannel
+ == remoteDevice.getStaticField<jint>("android/bluetooth/BluetoothDevice", "ERROR")
+ || socketChannel == -1) {
+ qCWarning(QT_BT_ANDROID) << "Cannot determine RFCOMM service channel.";
+ } else {
+ qCWarning(QT_BT_ANDROID) << "Using found rfcomm channel" << socketChannel;
+ channel = socketChannel;
+ }
}
}
diff --git a/src/bluetooth/qbluetoothsocket_osx.mm b/src/bluetooth/qbluetoothsocket_osx.mm
index 69663e58..75712868 100644
--- a/src/bluetooth/qbluetoothsocket_osx.mm
+++ b/src/bluetooth/qbluetoothsocket_osx.mm
@@ -452,7 +452,7 @@ void QBluetoothSocket::connectToService(const QBluetoothServiceInfo &service, Op
}
if (state() != UnconnectedState && state() != ServiceLookupState) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called on a busy socket";
+ qCWarning(QT_BT_OSX) << "called on a busy socket";
d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS);
setSocketError(OperationError);
return;
@@ -468,8 +468,8 @@ void QBluetoothSocket::connectToService(const QBluetoothServiceInfo &service, Op
} else {
// Try service discovery.
if (service.serviceUuid().isNull()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "No port, "
- "no PSM, and no UUID provided, unable to connect";
+ qCWarning(QT_BT_OSX) << "No port, no PSM, and no "
+ "UUID provided, unable to connect";
return;
}
@@ -489,7 +489,7 @@ void QBluetoothSocket::connectToService(const QBluetoothAddress &address, const
}
if (state() != QBluetoothSocket::UnconnectedState) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called on a busy socket";
+ qCWarning(QT_BT_OSX) << "called on a busy socket";
d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS);
setSocketError(QBluetoothSocket::OperationError);
return;
@@ -513,7 +513,7 @@ void QBluetoothSocket::connectToService(const QBluetoothAddress &address, quint1
}
if (state() != QBluetoothSocket::UnconnectedState) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "called on a busy socket";
+ qCWarning(QT_BT_OSX) << "called on a busy socket";
d_ptr->errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS);
setSocketError(OperationError);
return;
@@ -554,7 +554,7 @@ void QBluetoothSocket::setSocketState(QBluetoothSocket::SocketState state)
// We can register for L2CAP/RFCOMM open notifications,
// that's different from 'listen' and is implemented
// in QBluetoothServer.
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "listening sockets are not supported";
+ qCWarning(QT_BT_OSX) << "listening sockets are not supported";
}
}
diff --git a/src/bluetooth/qbluetoothtransferreply_osx.mm b/src/bluetooth/qbluetoothtransferreply_osx.mm
index 99c6cab1..02133860 100644
--- a/src/bluetooth/qbluetoothtransferreply_osx.mm
+++ b/src/bluetooth/qbluetoothtransferreply_osx.mm
@@ -177,7 +177,7 @@ void QBluetoothTransferReplyOSXPrivate::sendConnect(const QBluetoothAddress &dev
errorString.clear();
if (device.isNull() || !channelID) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid device address or port";
+ qCWarning(QT_BT_OSX) << "invalid device address or port";
setReplyError(QBluetoothTransferReply::HostNotFoundError,
QCoreApplication::translate(TRANSFER_REPLY, TR_INVAL_TARGET));
return;
@@ -186,7 +186,7 @@ void QBluetoothTransferReplyOSXPrivate::sendConnect(const QBluetoothAddress &dev
OBEXSession newSession([[ObjCOBEXSession alloc] initWithDelegate:this
remoteDevice:device channelID:channelID]);
if (!newSession) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to allocate OSXBTOBEXSession object";
+ qCWarning(QT_BT_OSX) << "failed to allocate OSXBTOBEXSession object";
setReplyError(QBluetoothTransferReply::UnknownError,
QCoreApplication::translate(TRANSFER_REPLY, TR_SESSION_NO_START));
@@ -201,7 +201,7 @@ void QBluetoothTransferReplyOSXPrivate::sendConnect(const QBluetoothAddress &dev
if ([session isConnected])
sendPut();// Connected, send a PUT request.
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "OBEXConnect failed";
+ qCWarning(QT_BT_OSX) << "OBEXConnect failed";
if (error == QBluetoothTransferReply::NoError) {
// The error is not set yet.
@@ -355,7 +355,7 @@ QBluetoothTransferReplyOSX::QBluetoothTransferReplyOSX(QIODevice *input,
if (input) {
QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection);
} else {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid input stream (null)";
+ qCWarning(QT_BT_OSX) << "invalid input stream (null)";
osx_d_ptr->requestComplete = true;
osx_d_ptr->errorString = QCoreApplication::translate(TRANSFER_REPLY, TR_INVALID_DEVICE);
osx_d_ptr->error = FileNotFoundError;
@@ -416,7 +416,7 @@ bool QBluetoothTransferReplyOSX::start()
if (!osx_d_ptr->isActive()) {
// Step 0: find a channelID.
if (request().address().isNull()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid device address";
+ qCWarning(QT_BT_OSX) << "invalid device address";
osx_d_ptr->setReplyError(HostNotFoundError,
QCoreApplication::translate(TRANSFER_REPLY, TR_INVAL_TARGET));
return false;
@@ -455,7 +455,8 @@ void QBluetoothTransferReplyOSX::serviceDiscoveryFinished()
void QBluetoothTransferReplyOSX::serviceDiscoveryError(QBluetoothServiceDiscoveryAgent::Error errorCode)
{
- Q_ASSERT_X(osx_d_ptr->agent.data(), Q_FUNC_INFO, "invalid service discovery agent (null)");
+ Q_ASSERT_X(osx_d_ptr->agent.data(), Q_FUNC_INFO,
+ "invalid service discovery agent (null)");
if (errorCode == QBluetoothServiceDiscoveryAgent::PoweredOffError) {
// There's nothing else we can do.
diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp
index 6581c4ad..03278276 100644
--- a/src/bluetooth/qlowenergycontroller.cpp
+++ b/src/bluetooth/qlowenergycontroller.cpp
@@ -198,13 +198,17 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT)
This signal is emitted when the controller successfully connects to the remote
Low Energy device (if the controller is in the \l CentralRole) or if a remote Low Energy
device connected to the controller (if the controller is in the \l PeripheralRole).
+ On iOS and OS X this signal is not reliable if the controller is in the \l PeripheralRole
+ - the controller only guesses that some central connected to our peripheral as
+ soon as this central tries to write/read a characteristic/descriptor.
*/
/*!
\fn void QLowEnergyController::disconnected()
This signal is emitted when the controller disconnects from the remote
- Low Energy device or vice versa.
+ Low Energy device or vice versa. On iOS and OS X this signal is unreliable
+ if the controller is in the \l PeripheralRole.
*/
/*!
@@ -308,6 +312,9 @@ void QLowEnergyControllerPrivate::setError(
bool QLowEnergyControllerPrivate::isValidLocalAdapter()
{
+#ifdef QT_WINRT_BLUETOOTH
+ return true;
+#endif
if (localAdapter.isNull())
return false;
@@ -628,6 +635,23 @@ QBluetoothAddress QLowEnergyController::remoteAddress() const
}
/*!
+ Returns the unique identifier of the remote Bluetooth Low Energy device.
+
+ On macOS/iOS/tvOS CoreBluetooth does not expose/accept hardware addresses for
+ LE devices; instead developers are supposed to use unique 128-bit UUIDs, generated
+ by CoreBluetooth. These UUIDS will stay constant for the same central <-> peripheral
+ pair and we use them when connecting to a remote device. For a controller in the
+ \l CentralRole, this value will always be the one passed in when the controller
+ object was created. For a controller in the \l PeripheralRole, this value is invalid.
+
+ \since 5.8
+ */
+QBluetoothUuid QLowEnergyController::remoteDeviceUuid() const
+{
+ return QBluetoothUuid();
+}
+
+/*!
Returns the name of the remote Bluetooth Low Energy device, if the controller is in the
\l CentralRole. Otherwise the result is unspecified.
@@ -687,6 +711,11 @@ void QLowEnergyController::connectToDevice()
{
Q_D(QLowEnergyController);
+ if (role() != CentralRole) {
+ qCWarning(QT_BT) << "Connection can only be established while in central role";
+ return;
+ }
+
if (!d->isValidLocalAdapter()) {
d->setError(QLowEnergyController::InvalidBluetoothAdapterError);
return;
@@ -708,6 +737,10 @@ void QLowEnergyController::connectToDevice()
This function does nothing if the controller is in the \l UnconnectedState.
+ If the controller is in the peripheral role, it stops advertising too.
+ The application must restart the advertising mode by calling
+ \l startAdvertising().
+
\sa connectToDevice()
*/
void QLowEnergyController::disconnectFromDevice()
diff --git a/src/bluetooth/qlowenergycontroller.h b/src/bluetooth/qlowenergycontroller.h
index 4ee07531..1c4fa83f 100644
--- a/src/bluetooth/qlowenergycontroller.h
+++ b/src/bluetooth/qlowenergycontroller.h
@@ -107,6 +107,7 @@ public:
QBluetoothAddress localAddress() const;
QBluetoothAddress remoteAddress() const;
+ QBluetoothUuid remoteDeviceUuid() const;
QString remoteName() const;
diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp
index e85fcb34..f8d2ba2a 100644
--- a/src/bluetooth/qlowenergycontroller_bluez.cpp
+++ b/src/bluetooth/qlowenergycontroller_bluez.cpp
@@ -48,6 +48,7 @@
#include <QtCore/QFileInfo>
#include <QtCore/QLoggingCategory>
#include <QtCore/QSettings>
+#include <QtCore/QTimer>
#include <QtBluetooth/QBluetoothLocalDevice>
#include <QtBluetooth/QBluetoothSocket>
#include <QtBluetooth/QLowEnergyCharacteristicData>
@@ -132,6 +133,13 @@
#define ATT_ERROR_UNSUPPRTED_GROUP_TYPE 0x10
#define ATT_ERROR_INSUF_RESOURCES 0x11
#define ATT_ERROR_APPLICATION_START 0x80
+//------------------------------------------
+// The error codes in this block are
+// implementation specific errors
+
+#define ATT_ERROR_REQUEST_STALLED 0x81
+
+//------------------------------------------
#define ATT_ERROR_APPLICATION_END 0x9f
#define APPEND_VALUE true
@@ -211,7 +219,7 @@ static void dumpErrorInformation(const QByteArray &response)
errorString = QStringLiteral("insufficient resources to complete request"); break;
default:
if (errorCode >= ATT_ERROR_APPLICATION_START && errorCode <= ATT_ERROR_APPLICATION_END)
- errorString = QStringLiteral("application error");
+ errorString = QStringLiteral("application error: %1").arg(errorCode);
else
errorString = QStringLiteral("unknown error code");
break;
@@ -304,6 +312,100 @@ void QLowEnergyControllerPrivate::init()
signingData.insert(remoteDevice.toUInt64(), SigningData(csrk));
}
);
+
+ if (role == QLowEnergyController::CentralRole) {
+ if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty("BLUETOOTH_GATT_TIMEOUT"))) {
+ bool ok = false;
+ int value = qEnvironmentVariableIntValue("BLUETOOTH_GATT_TIMEOUT", &ok);
+ if (ok)
+ gattRequestTimeout = value;
+ }
+
+ // permit disabling of timeout behavior via environment variable
+ if (gattRequestTimeout > 0) {
+ qCWarning(QT_BT_BLUEZ) << "Enabling GATT request timeout behavior" << gattRequestTimeout;
+ requestTimer = new QTimer(this);
+ requestTimer->setSingleShot(true);
+ requestTimer->setInterval(gattRequestTimeout);
+ connect(requestTimer, &QTimer::timeout,
+ this, &QLowEnergyControllerPrivate::handleGattRequestTimeout);
+ }
+ }
+}
+
+void QLowEnergyControllerPrivate::handleGattRequestTimeout()
+{
+ // antyhing open that might require cancellation or a warning?
+ if (encryptionChangePending) {
+ // We cannot really recover for now but the warning is essential for debugging
+ qCWarning(QT_BT_BLUEZ) << "****** Encryption change event blocking further GATT requests";
+ return;
+ }
+
+ if (!openRequests.isEmpty() && requestPending) {
+ requestPending = false; // reset pending flag
+ const Request currentRequest = openRequests.dequeue();
+
+ qCWarning(QT_BT_BLUEZ).nospace() << "****** Request type 0x" << hex << currentRequest.command
+ << " to server/peripheral timed out";
+ qCWarning(QT_BT_BLUEZ) << "****** Looks like the characteristic or descriptor does NOT act in"
+ << "accordance to Bluetooth 4.x spec.";
+ qCWarning(QT_BT_BLUEZ) << "****** Please check server implementation."
+ << "Continuing under reservation.";
+
+ quint8 command = currentRequest.command;
+ const auto createRequestErrorMessage = [](quint8 opcodeWithError,
+ QLowEnergyHandle handle) {
+ QByteArray errorPackage(ERROR_RESPONSE_HEADER_SIZE, Qt::Uninitialized);
+ errorPackage[0] = ATT_OP_ERROR_RESPONSE;
+ errorPackage[1] = opcodeWithError; // e.g. ATT_OP_READ_REQUEST
+ putBtData(handle, errorPackage.data() + 2); //
+ errorPackage[4] = ATT_ERROR_REQUEST_STALLED;
+
+ return errorPackage;
+ };
+
+ switch (command) {
+ case ATT_OP_EXCHANGE_MTU_REQUEST: // MTU change request
+ // never received reply to MTU request
+ // it is safe to skip and go to next request
+ sendNextPendingRequest();
+ break;
+ case ATT_OP_READ_BY_GROUP_REQUEST: // primary or secondary service discovery
+ case ATT_OP_READ_BY_TYPE_REQUEST: // characteristic or included service discovery
+ // jump back into usual response handling with custom error code
+ // 2nd param "0" as required by spec
+ processReply(currentRequest, createRequestErrorMessage(command, 0));
+ break;
+ case ATT_OP_READ_REQUEST: // read descriptor or characteristic value
+ case ATT_OP_READ_BLOB_REQUEST: // read long descriptor or characteristic
+ case ATT_OP_WRITE_REQUEST: // write descriptor or characteristic
+ {
+ uint handleData = currentRequest.reference.toUInt();
+ const QLowEnergyHandle charHandle = (handleData & 0xffff);
+ const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff);
+ processReply(currentRequest, createRequestErrorMessage(command,
+ descriptorHandle ? descriptorHandle : charHandle));
+ }
+ break;
+ case ATT_OP_FIND_INFORMATION_REQUEST: // get descriptor information
+ processReply(currentRequest, createRequestErrorMessage(
+ command, currentRequest.reference2.toUInt()));
+ break;
+ case ATT_OP_PREPARE_WRITE_REQUEST: // prepare to write long desc or char
+ case ATT_OP_EXECUTE_WRITE_REQUEST: // execute long write of desc or char
+ {
+ uint handleData = currentRequest.reference.toUInt();
+ const QLowEnergyHandle attrHandle = (handleData & 0xffff);
+ processReply(currentRequest,
+ createRequestErrorMessage(command, attrHandle));
+ }
+ break;
+ default:
+ // not a command used by central role implementation
+ return;
+ }
+ }
}
QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate()
@@ -537,6 +639,19 @@ void QLowEnergyControllerPrivate::resetController()
receivedMtuExchangeRequest = false;
securityLevelValue = -1;
connectionHandle = 0;
+
+ // public API behavior requires stop of advertisement
+ if (role == QLowEnergyController::PeripheralRole && advertiser)
+ advertiser->stopAdvertising();
+}
+
+void QLowEnergyControllerPrivate::restartRequestTimer()
+{
+ if (!requestTimer)
+ return;
+
+ if (gattRequestTimeout > 0)
+ requestTimer->start(gattRequestTimeout);
}
void QLowEnergyControllerPrivate::l2cpReadyRead()
@@ -564,7 +679,8 @@ void QLowEnergyControllerPrivate::l2cpReadyRead()
processUnsolicitedReply(incomingPacket);
return;
}
-
+ //--------------------------------------------------
+ // Peripheral side packet handling
case ATT_OP_EXCHANGE_MTU_REQUEST:
handleExchangeMtuRequest(incomingPacket);
return;
@@ -608,6 +724,7 @@ void QLowEnergyControllerPrivate::l2cpReadyRead()
qCWarning(QT_BT_BLUEZ) << "received unexpected handle value confirmation";
}
return;
+ //--------------------------------------------------
default:
//only solicited replies finish pending requests
requestPending = false;
@@ -713,6 +830,7 @@ void QLowEnergyControllerPrivate::sendNextPendingRequest()
// << request.payload.toHex();
requestPending = true;
+ restartRequestTimer();
sendPacket(request.payload);
}
@@ -1920,8 +2038,10 @@ bool QLowEnergyControllerPrivate::increaseEncryptLevelfRequired(quint8 errorCode
return false;
if (securityLevelValue != BT_SECURITY_HIGH) {
qCDebug(QT_BT_BLUEZ) << "Requesting encrypted link";
- if (setSecurityLevel(BT_SECURITY_HIGH))
+ if (setSecurityLevel(BT_SECURITY_HIGH)) {
+ restartRequestTimer();
return true;
+ }
}
break;
default:
diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm
index 47f65965..a2923d81 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"
@@ -51,7 +54,6 @@
#include <QtCore/qloggingcategory.h>
#include <QtCore/qsharedpointer.h>
#include <QtCore/qbytearray.h>
-#include <QtCore/qsysinfo.h>
#include <QtCore/qglobal.h>
#include <QtCore/qstring.h>
#include <QtCore/qlist.h>
@@ -84,8 +86,7 @@ ServicePrivate qt_createLEService(QLowEnergyControllerPrivateOSX *controller, CB
CBUUID *const cbUuid = cbService.UUID;
if (!cbUuid) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "invalid service, "
- "UUID is nil";
+ qCDebug(QT_BT_OSX) << "invalid service, UUID is nil";
return ServicePrivate();
}
@@ -102,18 +103,12 @@ ServicePrivate qt_createLEService(QLowEnergyControllerPrivateOSX *controller, CB
// TODO: isPrimary is ... always 'NO' - to be investigated.
/*
- #if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_6_0)
- using OSXBluetooth::qt_OS_limit;
- if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_6_0)) {
- if (!cbService.isPrimary) {
- // Our guess included/not was probably wrong.
- newService->type &= ~QLowEnergyService::PrimaryService;
- newService->type |= QLowEnergyService::IncludedService;
- }
+ if (!cbService.isPrimary) {
+ // Our guess included/not was probably wrong.
+ newService->type &= ~QLowEnergyService::PrimaryService;
+ newService->type |= QLowEnergyService::IncludedService;
}
- #endif
*/
- // No such property before 10_9/6_0.
return newService;
}
@@ -136,36 +131,8 @@ UUIDList qt_servicesUuids(NSArray *services)
}
-QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController *q)
- : q_ptr(q),
- 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;
-
- // 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";
- }
-
- // Ownership was taken by central manager.
- notifier.take();
-}
-
-QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController *q,
+QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyController::Role r,
+ QLowEnergyController *q,
const QBluetoothDeviceInfo &deviceInfo)
: q_ptr(q),
deviceUuid(deviceInfo.deviceUuid()),
@@ -178,44 +145,67 @@ QLowEnergyControllerPrivateOSX::QLowEnergyControllerPrivateOSX(QLowEnergyControl
Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)");
- using OSXBluetooth::LECentralNotifier;
+ using OSXBluetooth::LECBManagerNotifier;
+
+ role = r;
- 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";
+ QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier);
+ if (role == QLowEnergyController::PeripheralRole) {
+#ifndef Q_OS_TVOS
+ peripheralManager.reset([[ObjCPeripheralManager alloc] initWith:notifier.data()]);
+ if (!peripheralManager) {
+ qCWarning(QT_BT_OSX) << "failed to initialize peripheral manager";
+ return;
+ }
+#else
+ qCWarning(QT_BT_OSX) << "peripheral role is not supported on your platform";
return;
- } else if (!connectSlots(notifier.data())) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "failed to connect to notifier's signals";
+#endif
+ } else {
+ centralManager.reset([[ObjCCentralManager alloc] initWith:notifier.data()]);
+ if (!centralManager) {
+ qCWarning(QT_BT_OSX) << "failed to initialize central manager";
+ return;
+ }
}
+ if (!connectSlots(notifier.data())) {
+ qCWarning(QT_BT_OSX) << "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 {
+#ifndef Q_OS_TVOS
+ const auto manager = peripheralManager.data();
+ dispatch_sync(leQueue, ^{
+ [manager detach];
+ });
+#endif
+ }
}
}
bool QLowEnergyControllerPrivateOSX::isValid() const
{
+#ifdef Q_OS_TVOS
return centralManager;
+#else
+ return centralManager || peripheralManager;
+#endif
}
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 +216,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()
@@ -258,7 +249,7 @@ void QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished()
continue;
if (discoveredServices.contains(newService->uuid)) {
// It's a bit stupid we first created it ...
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "discovered service with a duplicated UUID "
+ qCDebug(QT_BT_OSX) << "discovered service with a duplicated UUID"
<< newService->uuid;
continue;
}
@@ -310,7 +301,7 @@ void QLowEnergyControllerPrivateOSX::_q_serviceDiscoveryFinished()
toVisitNext.resetWithoutRetain([[NSMutableArray alloc] init]);
}
} else {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no services found";
+ qCDebug(QT_BT_OSX) << "no services found";
}
for (ServiceMap::const_iterator it = discoveredServices.constBegin(); it != discoveredServices.constEnd(); ++it) {
@@ -332,7 +323,7 @@ void QLowEnergyControllerPrivateOSX::_q_serviceDetailsDiscoveryFinished(QSharedP
Q_ASSERT(service);
if (!discoveredServices.contains(service->uuid)) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "unknown service uuid: "
+ qCDebug(QT_BT_OSX) << "unknown service uuid:"
<< service->uuid;
return;
}
@@ -357,7 +348,7 @@ void QLowEnergyControllerPrivateOSX::_q_characteristicRead(QLowEnergyHandle char
QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle));
if (!characteristic.isValid()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown characteristic";
+ qCWarning(QT_BT_OSX) << "unknown characteristic";
return;
}
@@ -374,14 +365,14 @@ void QLowEnergyControllerPrivateOSX::_q_characteristicWritten(QLowEnergyHandle c
ServicePrivate service(serviceForHandle(charHandle));
if (service.isNull()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "can not find service for characteristic handle "
+ qCWarning(QT_BT_OSX) << "can not find service for characteristic handle"
<< charHandle;
return;
}
QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle));
if (!characteristic.isValid()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown characteristic";
+ qCWarning(QT_BT_OSX) << "unknown characteristic";
return;
}
@@ -410,7 +401,7 @@ void QLowEnergyControllerPrivateOSX::_q_characteristicUpdated(QLowEnergyHandle c
QLowEnergyCharacteristic characteristic(characteristicForHandle(charHandle));
if (!characteristic.isValid()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown characteristic";
+ qCWarning(QT_BT_OSX) << "unknown characteristic";
return;
}
@@ -427,7 +418,7 @@ void QLowEnergyControllerPrivateOSX::_q_descriptorRead(QLowEnergyHandle dHandle,
const QLowEnergyDescriptor qtDescriptor(descriptorForHandle(dHandle));
if (!qtDescriptor.isValid()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown descriptor " << dHandle;
+ qCWarning(QT_BT_OSX) << "unknown descriptor" << dHandle;
return;
}
@@ -443,7 +434,7 @@ void QLowEnergyControllerPrivateOSX::_q_descriptorWritten(QLowEnergyHandle dHand
const QLowEnergyDescriptor qtDescriptor(descriptorForHandle(dHandle));
if (!qtDescriptor.isValid()) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown descriptor " << dHandle;
+ qCWarning(QT_BT_OSX) << "unknown descriptor" << dHandle;
return;
}
@@ -453,6 +444,49 @@ void QLowEnergyControllerPrivateOSX::_q_descriptorWritten(QLowEnergyHandle dHand
emit service->descriptorWritten(qtDescriptor, value);
}
+void QLowEnergyControllerPrivateOSX::_q_notificationEnabled(QLowEnergyHandle charHandle,
+ bool enabled)
+{
+ // CoreBluetooth in peripheral role does not allow mutable descriptors,
+ // in central we can only call setNotification:enabled/disabled.
+ // But from Qt API's point of view, a central has to write into
+ // client characteristic configuration descriptor. So here we emulate
+ // such a write (we cannot say if it's a notification or indication and
+ // report as both).
+
+ Q_ASSERT_X(role == QLowEnergyController::PeripheralRole, Q_FUNC_INFO,
+ "controller has an invalid role, 'peripheral' expected");
+ Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle (0)");
+
+ const QLowEnergyCharacteristic qtChar(characteristicForHandle(charHandle));
+ if (!qtChar.isValid()) {
+ qCWarning(QT_BT_OSX) << "unknown characteristic" << charHandle;
+ return;
+ }
+
+ const QLowEnergyDescriptor qtDescriptor =
+ qtChar.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
+ if (!qtDescriptor.isValid()) {
+ qCWarning(QT_BT_OSX) << "characteristic" << charHandle
+ << "does not have a client characteristic "
+ "descriptor";
+ return;
+ }
+
+ ServicePrivate service(serviceForHandle(charHandle));
+ if (service.data()) {
+ // It's a 16-bit value, the least significant bit is for notifications,
+ // the next one - for indications (thus 1 means notifications enabled,
+ // 2 - indications enabled).
+ // 3 is the maximum value and it means both enabled.
+ QByteArray value(2, 0);
+ if (enabled)
+ value[0] = 3;
+ updateValueOfDescriptor(charHandle, qtDescriptor.handle(), value, false);
+ emit service->descriptorWritten(qtDescriptor, value);
+ }
+}
+
void QLowEnergyControllerPrivateOSX::_q_LEnotSupported()
{
// Report as an error. But this should not be possible
@@ -461,7 +495,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 +512,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?
@@ -489,16 +523,16 @@ void QLowEnergyControllerPrivateOSX::_q_CBCentralManagerError(const QBluetoothUu
ServicePrivate qtService(discoveredServices.value(serviceUuid));
qtService->setState(QLowEnergyService::InvalidService);
} else {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "error reported for unknown service "
+ qCDebug(QT_BT_OSX) << "error reported for unknown service"
<< serviceUuid;
}
}
-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: "
+ qCDebug(QT_BT_OSX) << "unknown service uuid:"
<< serviceUuid;
return;
}
@@ -514,10 +548,12 @@ 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) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
setErrorDescription(QLowEnergyController::UnknownError);
return;
}
@@ -537,10 +573,12 @@ 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) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
setErrorDescription(QLowEnergyController::UnknownError);
return;
}
@@ -559,20 +597,21 @@ void QLowEnergyControllerPrivateOSX::discoverServiceDetails(const QBluetoothUuid
Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid private controller");
if (controllerState != QLowEnergyController::DiscoveredState) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO
- << "can not discover service details in the current state, "
- << "QLowEnergyController::DiscoveredState is expected";
+ // This will also exclude peripheral role, since controller
+ // can never be in discovered state ...
+ qCWarning(QT_BT_OSX) << "can not discover service details in the current state, "
+ "QLowEnergyController::DiscoveredState is expected";
return;
}
if (!discoveredServices.contains(serviceUuid)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unknown service: " << serviceUuid;
+ qCWarning(QT_BT_OSX) << "unknown service: " << serviceUuid;
return;
}
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
return;
}
@@ -593,32 +632,37 @@ 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) << "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,
// but instead call a special method. So it's better to
// intercept wrong data size here:
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "client characteristic configuration descriptor "
+ qCWarning(QT_BT_OSX) << "client characteristic configuration descriptor"
"is 2 bytes, but value size is: " << newValue.size();
service->setError(QLowEnergyService::DescriptorWriteError);
return;
}
if (!discoveredServices.contains(service->uuid)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid: "
- << service->uuid << " found";
+ qCWarning(QT_BT_OSX) << "no service with uuid:" << service->uuid << "found";
return;
}
if (!service->characteristicList.contains(charHandle)) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no characteristic with handle: "
- << charHandle << " found";
+ qCDebug(QT_BT_OSX) << "no characteristic with handle:"
+ << charHandle << "found";
return;
}
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
return;
}
ObjCCentralManager *manager = centralManager.data();
@@ -637,21 +681,26 @@ 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) << "invalid role (peripheral)";
+ return;
+ }
+
if (!discoveredServices.contains(service->uuid)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid:"
+ qCWarning(QT_BT_OSX) << "no service with uuid:"
<< service->uuid << "found";
return;
}
if (!service->characteristicList.contains(charHandle)) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no characteristic with handle:"
+ qCDebug(QT_BT_OSX) << "no characteristic with handle:"
<< charHandle << "found";
return;
}
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
return;
}
// Attention! We have to copy UUID.
@@ -669,36 +718,47 @@ 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: "
+ qCWarning(QT_BT_OSX) << "no service with uuid:"
<< service->uuid << " found";
return;
}
if (!service->characteristicList.contains(charHandle)) {
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "no characteristic with handle: "
+ qCDebug(QT_BT_OSX) << "no characteristic with handle:"
<< charHandle << " found";
return;
}
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "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 {
+#ifndef Q_OS_TVOS
+ const auto manager = peripheralManager.data();
+ dispatch_async(leQueue, ^{
+ [manager write:newValueCopy charHandle:charHandle];
+ });
+#else
+ qCWarning(QT_BT_OSX) << "peripheral role is not supported on your platform";
+#endif
+ }
}
quint16 QLowEnergyControllerPrivateOSX::updateValueOfCharacteristic(QLowEnergyHandle charHandle,
@@ -728,15 +788,20 @@ 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) << "invalid role (peripheral)";
+ return;
+ }
+
if (!discoveredServices.contains(service->uuid)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid:"
+ qCWarning(QT_BT_OSX) << "no service with uuid:"
<< service->uuid << "found";
return;
}
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
return;
}
// Attention! Copy objects!
@@ -755,18 +820,23 @@ 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) << "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.
if (!discoveredServices.contains(service->uuid)) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no service with uuid: "
+ qCWarning(QT_BT_OSX) << "no service with uuid:"
<< service->uuid << " found";
return;
}
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
if (!leQueue) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no LE queue found";
+ qCWarning(QT_BT_OSX) << "no LE queue found";
return;
}
// Attention! Copy objects!
@@ -868,17 +938,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 +969,40 @@ 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::notificationEnabled,
+ this, &QLowEnergyControllerPrivateOSX::_q_notificationEnabled);
+ 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,26 +1013,24 @@ 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();
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "construction with remote address "
+ qCWarning(QT_BT_OSX) << "construction with remote address "
"is not supported!";
}
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,24 +1040,23 @@ 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;
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "construction with remote/local "
+ qCWarning(QT_BT_OSX) << "construction with remote/local "
"addresses is not supported!";
}
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();
}
@@ -1023,6 +1098,13 @@ QBluetoothAddress QLowEnergyController::remoteAddress() const
return osx_d_ptr->remoteAddress;
}
+QBluetoothUuid QLowEnergyController::remoteDeviceUuid() const
+{
+ OSX_D_PTR;
+
+ return osx_d_ptr->deviceUuid;
+}
+
QString QLowEnergyController::remoteName() const
{
OSX_D_PTR;
@@ -1059,11 +1141,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 +1165,13 @@ void QLowEnergyController::disconnectFromDevice()
OSX_D_PTR;
+ if (role() == PeripheralRole) {
+ // CoreBluetooth API intentionally does not provide any way of closing
+ // a connection. All we can do here is to stop the advertisement.
+ stopAdvertising();
+ return;
+ }
+
if (osx_d_ptr->isValid()) {
const ControllerState oldState = osx_d_ptr->controllerState;
@@ -1101,14 +1195,19 @@ void QLowEnergyController::disconnectFromDevice()
emit stateChanged(UnconnectedState);
}
} else {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "qt LE queue is nil,"
- << "can not dispatch 'disconnect'";
+ qCCritical(QT_BT_OSX) << "qt LE queue is nil, "
+ "can not dispatch 'disconnect'";
}
}
}
void QLowEnergyController::discoverServices()
{
+ if (role() == PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "invalid role (peripheral)";
+ return;
+ }
+
if (state() != ConnectedState)
return;
@@ -1159,30 +1258,126 @@ 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";
+#ifdef Q_OS_TVOS
+ Q_UNUSED(params)
+ Q_UNUSED(advertisingData)
+ Q_UNUSED(scanResponseData)
+ qCWarning(QT_BT_OSX) << "advertising is not supported on your platform";
+#else
+ OSX_D_PTR;
+
+ if (!osx_d_ptr->isValid())
+ return osx_d_ptr->_q_CBManagerError(UnknownError);
+
+ if (role() != PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "invalid role";
+ return;
+ }
+
+ if (state() != UnconnectedState) {
+ qCWarning(QT_BT_OSX) << "invalid state" << state();
+ return;
+ }
+
+ auto leQueue(OSXBluetooth::qt_LE_queue());
+ if (!leQueue) {
+ qCWarning(QT_BT_OSX) << "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];
+ });
+#endif
}
void QLowEnergyController::stopAdvertising()
{
- qCWarning(QT_BT_OSX) << "LE advertising not implemented for OS X";
+#ifdef Q_OS_TVOS
+ qCWarning(QT_BT_OSX) << "advertising is not supported on your platform";
+#else
+ OSX_D_PTR;
+
+ if (!osx_d_ptr->isValid())
+ return osx_d_ptr->_q_CBManagerError(UnknownError);
+
+ if (state() != AdvertisingState) {
+ qCDebug(QT_BT_OSX) << "cannot stop advertising, 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) << "no LE queue found";
+ osx_d_ptr->setErrorDescription(QLowEnergyController::UnknownError);
+ return;
+ }
+#endif
}
-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";
+#ifdef Q_OS_TVOS
+ Q_UNUSED(data)
+ Q_UNUSED(parent)
+ qCWarning(QT_BT_OSX) << "peripheral role is not supported on your platform";
+#else
+ OSX_D_PTR;
+
+ if (!osx_d_ptr->isValid()) {
+ osx_d_ptr->_q_CBManagerError(UnknownError);
+ return nullptr;
+ }
+
+ if (role() != PeripheralRole) {
+ qCWarning(QT_BT_OSX) << "not in peripheral role";
+ return nullptr;
+ }
+
+ if (state() != UnconnectedState) {
+ qCWarning(QT_BT_OSX) << "invalid state";
+ return nullptr;
+ }
+
+ if (!data.isValid()) {
+ qCWarning(QT_BT_OSX) << "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);
+ }
+#endif
+
return nullptr;
}
void QLowEnergyController::requestConnectionUpdate(const QLowEnergyConnectionParameters &params)
{
Q_UNUSED(params);
- qCWarning(QT_BT_OSX) << "Connection update not implemented for OS X";
+ qCWarning(QT_BT_OSX) << "Connection update not implemented on your platform";
}
QT_END_NAMESPACE
diff --git a/src/bluetooth/qlowenergycontroller_osx_p.h b/src/bluetooth/qlowenergycontroller_osx_p.h
index 853e1f9b..5cceb9e6 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;
@@ -103,20 +104,18 @@ private Q_SLOTS:
void _q_characteristicUpdated(QLowEnergyHandle charHandle, const QByteArray &value);
void _q_descriptorRead(QLowEnergyHandle descHandle, const QByteArray &value);
void _q_descriptorWritten(QLowEnergyHandle charHandle, const QByteArray &value);
+ void _q_notificationEnabled(QLowEnergyHandle charHandle, bool enabled);
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 +148,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 +169,12 @@ private:
typedef OSXBluetooth::ObjCScopedPointer<ObjCCentralManager> CentralManager;
CentralManager centralManager;
+#ifndef Q_OS_TVOS
+ typedef QT_MANGLE_NAMESPACE(OSXBTPeripheralManager) ObjCPeripheralManager;
+ typedef OSXBluetooth::ObjCScopedPointer<ObjCPeripheralManager> PeripheralManager;
+ PeripheralManager peripheralManager;
+#endif
+
typedef QMap<QBluetoothUuid, QSharedPointer<QLowEnergyServicePrivate> > ServiceMap;
typedef ServiceMap::const_iterator ConstServiceIterator;
typedef ServiceMap::iterator ServiceIterator;
diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h
index 2256025c..19d10567 100644
--- a/src/bluetooth/qlowenergycontroller_p.h
+++ b/src/bluetooth/qlowenergycontroller_p.h
@@ -82,6 +82,11 @@ QT_END_NAMESPACE
#elif defined(QT_ANDROID_BLUETOOTH)
#include <QtAndroidExtras/QAndroidJniObject>
#include "android/lowenergynotificationhub_p.h"
+#elif defined(QT_WINRT_BLUETOOTH)
+#include <wrl.h>
+#include <windows.devices.bluetooth.h>
+
+class QWinRTLowEnergyServiceHandler;
#endif
#include <functional>
@@ -89,6 +94,7 @@ QT_END_NAMESPACE
QT_BEGIN_NAMESPACE
class QLowEnergyServiceData;
+class QTimer;
#if defined(QT_BLUEZ_BLUETOOTH) && !defined(QT_BLUEZ_NO_BTLE)
class HciManager;
@@ -271,6 +277,24 @@ private:
HciManager *hciManager;
QLeAdvertiser *advertiser;
QSocketNotifier *serverSocketNotifier;
+ QTimer *requestTimer = nullptr;
+
+ /*
+ Defines the maximum number of milliseconds the implementation will
+ wait for requests that require a response.
+
+ This addresses the problem that some non-conformant BTLE devices
+ do not implement the request/response system properly. In such cases
+ the queue system would hang forever.
+
+ Once timeout has been triggered we gracefully continue with the next request.
+ Depending on the type of the timed out ATT command we either ignore it
+ or artifically trigger an error response to ensure the API gives the
+ appropriate response. Potentially this can cause problems when the
+ response for the dropped requests arrives very late. That's why a big warning
+ is printed about the compromised state when a timeout is triggered.
+ */
+ int gattRequestTimeout = 20000;
void handleConnectionRequest();
void closeServerSocket();
@@ -387,12 +411,15 @@ private:
const QLowEnergyHandle descriptorHandle,
const QByteArray &newValue);
+ void restartRequestTimer();
+
private slots:
void l2cpConnected();
void l2cpDisconnected();
void l2cpErrorChanged(QBluetoothSocket::SocketError);
void l2cpReadyRead();
void encryptionChangedEvent(const QBluetoothAddress&, bool);
+ void handleGattRequestTimeout();
#elif defined(QT_ANDROID_BLUETOOTH)
LowEnergyNotificationHub *hub;
@@ -414,6 +441,34 @@ private slots:
QLowEnergyService::ServiceError errorCode);
void characteristicChanged(int charHandle, const QByteArray &data);
void serviceError(int attributeHandle, QLowEnergyService::ServiceError errorCode);
+#elif defined(QT_WINRT_BLUETOOTH)
+private slots:
+ void characteristicChanged(int charHandle, const QByteArray &data);
+
+private:
+ Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice> mDevice;
+ EventRegistrationToken mStatusChangedToken;
+ struct ValueChangedEntry {
+ ValueChangedEntry() {}
+ ValueChangedEntry(Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattCharacteristic> c,
+ EventRegistrationToken t)
+ : characteristic(c)
+ , token(t)
+ {
+ }
+
+ Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattCharacteristic> characteristic;
+ EventRegistrationToken token;
+ };
+ QVector<ValueChangedEntry> mValueChangedTokens;
+
+ Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattDeviceService> getNativeService(const QBluetoothUuid &serviceUuid);
+ Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattCharacteristic> getNativeCharacteristic(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid);
+
+ void registerForValueChanges(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid);
+
+ void obtainIncludedServices(QSharedPointer<QLowEnergyServicePrivate> servicePointer,
+ Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattDeviceService> nativeService);
#endif
private:
QLowEnergyController *q_ptr;
@@ -424,6 +479,11 @@ Q_DECLARE_TYPEINFO(QLowEnergyControllerPrivate::Attribute, Q_MOVABLE_TYPE);
QT_END_NAMESPACE
+#ifdef QT_WINRT_BLUETOOTH
+Q_DECLARE_METATYPE(QLowEnergyCharacteristic)
+Q_DECLARE_METATYPE(QLowEnergyDescriptor)
+#endif // QT_WINRT_BLUETOOTH
+
#endif // QT_OSX_BLUETOOTH || QT_IOS_BLUETOOTH
#endif // QLOWENERGYCONTROLLERPRIVATE_P_H
diff --git a/src/bluetooth/qlowenergycontroller_winrt.cpp b/src/bluetooth/qlowenergycontroller_winrt.cpp
new file mode 100644
index 00000000..d6fc4952
--- /dev/null
+++ b/src/bluetooth/qlowenergycontroller_winrt.cpp
@@ -0,0 +1,1103 @@
+/****************************************************************************
+**
+** 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 "qlowenergycontroller_p.h"
+
+#include <QtCore/qfunctions_winrt.h>
+#include <QtCore/QLoggingCategory>
+#include <private/qeventdispatcher_winrt_p.h>
+
+#include <functional>
+#include <robuffer.h>
+#include <windows.devices.enumeration.h>
+#include <windows.devices.bluetooth.h>
+#include <windows.foundation.collections.h>
+#include <windows.storage.streams.h>
+
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::Foundation::Collections;
+using namespace ABI::Windows::Devices;
+using namespace ABI::Windows::Devices::Bluetooth;
+using namespace ABI::Windows::Devices::Bluetooth::GenericAttributeProfile;
+using namespace ABI::Windows::Devices::Enumeration;
+using namespace ABI::Windows::Storage::Streams;
+
+QT_BEGIN_NAMESPACE
+
+typedef ITypedEventHandler<BluetoothLEDevice *, IInspectable *> StatusHandler;
+typedef ITypedEventHandler<GattCharacteristic *, GattValueChangedEventArgs *> ValueChangedHandler;
+typedef GattReadClientCharacteristicConfigurationDescriptorResult ClientCharConfigDescriptorResult;
+typedef IGattReadClientCharacteristicConfigurationDescriptorResult IClientCharConfigDescriptorResult;
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT)
+
+static QVector<QBluetoothUuid> getIncludedServiceIds(const ComPtr<IGattDeviceService> &service)
+{
+ QVector<QBluetoothUuid> result;
+ ComPtr<IGattDeviceService2> service2;
+ HRESULT hr = service.As(&service2);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IVectorView<GattDeviceService *>> includedServices;
+ hr = service2->GetAllIncludedServices(&includedServices);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ uint count;
+ hr = includedServices->get_Size(&count);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (uint i = 0; i < count; ++i) {
+ ComPtr<IGattDeviceService> includedService;
+ hr = includedServices->GetAt(i, &includedService);
+ Q_ASSERT_SUCCEEDED(hr);
+ GUID guuid;
+ hr = includedService->get_Uuid(&guuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ const QBluetoothUuid service(guuid);
+ result << service;
+
+ result << getIncludedServiceIds(includedService);
+ }
+ return result;
+}
+
+static QByteArray byteArrayFromBuffer(const ComPtr<IBuffer> &buffer, bool isWCharString = false)
+{
+ ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess;
+ HRESULT hr = buffer.As(&byteAccess);
+ Q_ASSERT_SUCCEEDED(hr);
+ char *data;
+ hr = byteAccess->Buffer(reinterpret_cast<byte **>(&data));
+ Q_ASSERT_SUCCEEDED(hr);
+ UINT32 size;
+ hr = buffer->get_Length(&size);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (isWCharString) {
+ QString valueString = QString::fromUtf16(reinterpret_cast<ushort *>(data)).left(size / 2);
+ return valueString.toUtf8();
+ }
+ return QByteArray(data, size);
+}
+
+static QByteArray byteArrayFromGattResult(const ComPtr<IGattReadResult> &gattResult, bool isWCharString = false)
+{
+ ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
+ HRESULT hr;
+ hr = gattResult->get_Value(&buffer);
+ Q_ASSERT_SUCCEEDED(hr);
+ return byteArrayFromBuffer(buffer, isWCharString);
+}
+
+class QWinRTLowEnergyServiceHandler : public QObject
+{
+ Q_OBJECT
+public:
+ QWinRTLowEnergyServiceHandler(const QBluetoothUuid &service, const ComPtr<IGattDeviceService2> &deviceService)
+ : mService(service)
+ , mDeviceService(deviceService)
+ {
+ qCDebug(QT_BT_WINRT) << __FUNCTION__;
+ }
+
+ ~QWinRTLowEnergyServiceHandler()
+ {
+ }
+
+public slots:
+ void obtainCharList()
+ {
+ QVector<QBluetoothUuid> indicateChars;
+ quint16 startHandle = 0;
+ quint16 endHandle = 0;
+ qCDebug(QT_BT_WINRT) << __FUNCTION__;
+ ComPtr<IVectorView<GattCharacteristic *>> characteristics;
+ HRESULT hr = mDeviceService->GetAllCharacteristics(&characteristics);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (!characteristics) {
+ emit charListObtained(mService, mCharacteristicList, indicateChars, startHandle, endHandle);
+ QThread::currentThread()->quit();
+ return;
+ }
+
+ uint characteristicsCount;
+ hr = characteristics->get_Size(&characteristicsCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (uint i = 0; i < characteristicsCount; ++i) {
+ ComPtr<IGattCharacteristic> characteristic;
+ hr = characteristics->GetAt(i, &characteristic);
+ Q_ASSERT_SUCCEEDED(hr);
+ quint16 handle;
+ hr = characteristic->get_AttributeHandle(&handle);
+ Q_ASSERT_SUCCEEDED(hr);
+ QLowEnergyServicePrivate::CharData charData;
+ charData.valueHandle = handle + 1;
+ if (startHandle == 0 || startHandle > handle)
+ startHandle = handle;
+ if (endHandle == 0 || endHandle < handle)
+ endHandle = handle;
+ GUID guuid;
+ hr = characteristic->get_Uuid(&guuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ charData.uuid = QBluetoothUuid(guuid);
+ GattCharacteristicProperties properties;
+ hr = characteristic->get_CharacteristicProperties(&properties);
+ Q_ASSERT_SUCCEEDED(hr);
+ charData.properties = QLowEnergyCharacteristic::PropertyTypes(properties);
+ if (charData.properties & QLowEnergyCharacteristic::Read) {
+ ComPtr<IAsyncOperation<GattReadResult *>> readOp;
+ hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IGattReadResult> readResult;
+ hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ if (readResult)
+ charData.value = byteArrayFromGattResult(readResult);
+ }
+ ComPtr<IGattCharacteristic2> characteristic2;
+ hr = characteristic.As(&characteristic2);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IVectorView<GattDescriptor *>> descriptors;
+ hr = characteristic2->GetAllDescriptors(&descriptors);
+ Q_ASSERT_SUCCEEDED(hr);
+ uint descriptorCount;
+ hr = descriptors->get_Size(&descriptorCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (uint j = 0; j < descriptorCount; ++j) {
+ QLowEnergyServicePrivate::DescData descData;
+ ComPtr<IGattDescriptor> descriptor;
+ hr = descriptors->GetAt(j, &descriptor);
+ Q_ASSERT_SUCCEEDED(hr);
+ quint16 descHandle;
+ hr = descriptor->get_AttributeHandle(&descHandle);
+ Q_ASSERT_SUCCEEDED(hr);
+ GUID descriptorUuid;
+ hr = descriptor->get_Uuid(&descriptorUuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ descData.uuid = QBluetoothUuid(descriptorUuid);
+ if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) {
+ ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp;
+ hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IClientCharConfigDescriptorResult> readResult;
+ hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ GattClientCharacteristicConfigurationDescriptorValue value;
+ hr = readResult->get_ClientCharacteristicConfigurationDescriptor(&value);
+ Q_ASSERT_SUCCEEDED(hr);
+ quint16 result = 0;
+ bool correct = false;
+ if (value & GattClientCharacteristicConfigurationDescriptorValue_Indicate) {
+ result |= GattClientCharacteristicConfigurationDescriptorValue_Indicate;
+ correct = true;
+ }
+ if (value & GattClientCharacteristicConfigurationDescriptorValue_Notify) {
+ result |= GattClientCharacteristicConfigurationDescriptorValue_Notify;
+ correct = true;
+ }
+ if (value == GattClientCharacteristicConfigurationDescriptorValue_None) {
+ correct = true;
+ }
+ if (!correct)
+ continue;
+
+ descData.value = QByteArray(2, Qt::Uninitialized);
+ qToLittleEndian(result, descData.value.data());
+ indicateChars << charData.uuid;
+ } else {
+ ComPtr<IAsyncOperation<GattReadResult *>> readOp;
+ hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IGattReadResult> readResult;
+ hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ if (descData.uuid == QBluetoothUuid::CharacteristicUserDescription)
+ descData.value = byteArrayFromGattResult(readResult, true);
+ else
+ descData.value = byteArrayFromGattResult(readResult);
+ }
+ charData.descriptorList.insert(descHandle, descData);
+ }
+ mCharacteristicList.insert(handle, charData);
+ }
+ emit charListObtained(mService, mCharacteristicList, indicateChars, startHandle, endHandle);
+ QThread::currentThread()->quit();
+ }
+
+public:
+ QBluetoothUuid mService;
+ ComPtr<IGattDeviceService2> mDeviceService;
+ QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> mCharacteristicList;
+
+signals:
+ void charListObtained(const QBluetoothUuid &service, QHash<QLowEnergyHandle,
+ QLowEnergyServicePrivate::CharData> charList,
+ QVector<QBluetoothUuid> indicateChars,
+ QLowEnergyHandle startHandle, QLowEnergyHandle endHandle);
+};
+
+QLowEnergyControllerPrivate::QLowEnergyControllerPrivate()
+ : QObject(),
+ state(QLowEnergyController::UnconnectedState),
+ error(QLowEnergyController::NoError)
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__;
+
+ qRegisterMetaType<QLowEnergyCharacteristic>();
+ qRegisterMetaType<QLowEnergyDescriptor>();
+}
+
+QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate()
+{
+ if (mDevice && mStatusChangedToken.value)
+ mDevice->remove_ConnectionStatusChanged(mStatusChangedToken);
+
+ qCDebug(QT_BT_WINRT) << "Unregistering " << mValueChangedTokens.count() << " value change tokens";
+ for (const ValueChangedEntry &entry : mValueChangedTokens)
+ entry.characteristic->remove_ValueChanged(entry.token);
+}
+
+void QLowEnergyControllerPrivate::init()
+{
+}
+
+void QLowEnergyControllerPrivate::connectToDevice()
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__;
+ Q_Q(QLowEnergyController);
+ if (remoteDevice.isNull()) {
+ qWarning() << "Invalid/null remote device address";
+ setError(QLowEnergyController::UnknownRemoteDeviceError);
+ return;
+ }
+
+ setState(QLowEnergyController::ConnectingState);
+
+ ComPtr<IBluetoothLEDeviceStatics> deviceStatics;
+ HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &deviceStatics);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromIdOperation;
+ hr = deviceStatics->FromBluetoothAddressAsync(remoteDevice.toUInt64(), &deviceFromIdOperation);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = QWinRTFunctions::await(deviceFromIdOperation, mDevice.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+
+ if (!mDevice) {
+ qCDebug(QT_BT_WINRT) << "Could not find LE device";
+ setError(QLowEnergyController::InvalidBluetoothAdapterError);
+ setState(QLowEnergyController::UnconnectedState);
+ }
+ BluetoothConnectionStatus status;
+ hr = mDevice->get_ConnectionStatus(&status);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = QEventDispatcherWinRT::runOnXamlThread([this, q]() {
+ HRESULT hr;
+ hr = mDevice->add_ConnectionStatusChanged(Callback<StatusHandler>([this, q](IBluetoothLEDevice *dev, IInspectable *) {
+ BluetoothConnectionStatus status;
+ HRESULT hr;
+ hr = dev->get_ConnectionStatus(&status);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (state == QLowEnergyController::ConnectingState
+ && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) {
+ setState(QLowEnergyController::ConnectedState);
+ emit q->connected();
+ } else if (state == QLowEnergyController::ConnectedState
+ && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Disconnected) {
+ setState(QLowEnergyController::UnconnectedState);
+ emit q->disconnected();
+ }
+ return S_OK;
+ }).Get(), &mStatusChangedToken);
+ Q_ASSERT_SUCCEEDED(hr);
+ return S_OK;
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+
+ if (status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) {
+ setState(QLowEnergyController::ConnectedState);
+ emit q->connected();
+ return;
+ }
+
+ ComPtr<IVectorView <GattDeviceService *>> deviceServices;
+ hr = mDevice->get_GattServices(&deviceServices);
+ Q_ASSERT_SUCCEEDED(hr);
+ uint serviceCount;
+ hr = deviceServices->get_Size(&serviceCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ // Windows Phone automatically connects to the device as soon as a service value is read/written.
+ // Thus we read one value in order to establish the connection.
+ for (uint i = 0; i < serviceCount; ++i) {
+ ComPtr<IGattDeviceService> service;
+ hr = deviceServices->GetAt(i, &service);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IGattDeviceService2> service2;
+ hr = service.As(&service2);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IVectorView<GattCharacteristic *>> characteristics;
+ hr = service2->GetAllCharacteristics(&characteristics);
+ if (hr == E_ACCESSDENIED) {
+ // Everything will work as expected up until this point if the manifest capabilties
+ // for bluetooth LE are not set.
+ qCWarning(QT_BT_WINRT) << "Could not obtain characteristic list. Please check your "
+ "manifest capabilities";
+ setState(QLowEnergyController::UnconnectedState);
+ setError(QLowEnergyController::ConnectionError);
+ return;
+ } else {
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+ uint characteristicsCount;
+ hr = characteristics->get_Size(&characteristicsCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (uint j = 0; j < characteristicsCount; ++j) {
+ ComPtr<IGattCharacteristic> characteristic;
+ hr = characteristics->GetAt(j, &characteristic);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IAsyncOperation<GattReadResult *>> op;
+ GattCharacteristicProperties props;
+ hr = characteristic->get_CharacteristicProperties(&props);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (!(props & GattCharacteristicProperties_Read))
+ continue;
+ hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode::BluetoothCacheMode_Uncached, &op);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IGattReadResult> result;
+ hr = QWinRTFunctions::await(op, result.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
+ hr = result->get_Value(&buffer);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (!buffer) {
+ qCDebug(QT_BT_WINRT) << "Problem reading value";
+ setError(QLowEnergyController::ConnectionError);
+ setState(QLowEnergyController::UnconnectedState);
+ }
+ return;
+ }
+ }
+}
+
+void QLowEnergyControllerPrivate::disconnectFromDevice()
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__;
+ Q_Q(QLowEnergyController);
+ setState(QLowEnergyController::UnconnectedState);
+ emit q->disconnected();
+}
+
+ComPtr<IGattDeviceService> QLowEnergyControllerPrivate::getNativeService(const QBluetoothUuid &serviceUuid)
+{
+ ComPtr<IGattDeviceService> deviceService;
+ HRESULT hr;
+ hr = mDevice->GetGattService(serviceUuid, &deviceService);
+ if (FAILED(hr))
+ qCDebug(QT_BT_WINRT) << "Could not obtain native service for Uuid" << serviceUuid;
+ return deviceService;
+}
+
+ComPtr<IGattCharacteristic> QLowEnergyControllerPrivate::getNativeCharacteristic(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid)
+{
+ ComPtr<IGattDeviceService> service = getNativeService(serviceUuid);
+ if (!service)
+ return nullptr;
+
+ ComPtr<IVectorView<GattCharacteristic *>> characteristics;
+ HRESULT hr = service->GetCharacteristics(charUuid, &characteristics);
+ RETURN_IF_FAILED("Could not obtain native characteristics for service", return nullptr);
+ ComPtr<IGattCharacteristic> characteristic;
+ hr = characteristics->GetAt(0, &characteristic);
+ RETURN_IF_FAILED("Could not obtain first characteristic for service", return nullptr);
+ return characteristic;
+}
+
+void QLowEnergyControllerPrivate::registerForValueChanges(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid)
+{
+ qCDebug(QT_BT_WINRT) << "Registering characteristic" << charUuid << "in service"
+ << serviceUuid << "for value changes";
+ for (const ValueChangedEntry &entry : mValueChangedTokens) {
+ GUID guuid;
+ HRESULT hr;
+ hr = entry.characteristic->get_Uuid(&guuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (QBluetoothUuid(guuid) == charUuid)
+ return;
+ }
+ ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(serviceUuid, charUuid);
+
+ EventRegistrationToken token;
+ HRESULT hr;
+ hr = characteristic->add_ValueChanged(Callback<ValueChangedHandler>([this](IGattCharacteristic *characteristic, IGattValueChangedEventArgs *args) {
+ HRESULT hr;
+ quint16 handle;
+ hr = characteristic->get_AttributeHandle(&handle);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IBuffer> buffer;
+ hr = args->get_CharacteristicValue(&buffer);
+ Q_ASSERT_SUCCEEDED(hr);
+ characteristicChanged(handle, byteArrayFromBuffer(buffer));
+ return S_OK;
+ }).Get(), &token);
+ Q_ASSERT_SUCCEEDED(hr);
+ mValueChangedTokens.append(ValueChangedEntry(characteristic, token));
+ qCDebug(QT_BT_WINRT) << "Characteristic" << charUuid << "in service"
+ << serviceUuid << "registered for value changes";
+}
+
+void QLowEnergyControllerPrivate::obtainIncludedServices(QSharedPointer<QLowEnergyServicePrivate> servicePointer,
+ ComPtr<IGattDeviceService> service)
+{
+ Q_Q(QLowEnergyController);
+ ComPtr<IGattDeviceService2> service2;
+ HRESULT hr = service.As(&service2);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IVectorView<GattDeviceService *>> includedServices;
+ hr = service2->GetAllIncludedServices(&includedServices);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ uint count;
+ hr = includedServices->get_Size(&count);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (uint i = 0; i < count; ++i) {
+ ComPtr<IGattDeviceService> includedService;
+ hr = includedServices->GetAt(i, &includedService);
+ Q_ASSERT_SUCCEEDED(hr);
+ GUID guuid;
+ hr = includedService->get_Uuid(&guuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ const QBluetoothUuid includedUuid(guuid);
+ QSharedPointer<QLowEnergyServicePrivate> includedPointer;
+ if (serviceList.contains(includedUuid)) {
+ includedPointer = serviceList.value(includedUuid);
+ } else {
+ QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate();
+ priv->uuid = includedUuid;
+ priv->setController(this);
+
+ includedPointer = QSharedPointer<QLowEnergyServicePrivate>(priv);
+ serviceList.insert(includedUuid, includedPointer);
+ }
+ includedPointer->type |= QLowEnergyService::IncludedService;
+ servicePointer->includedServices.append(includedUuid);
+
+ obtainIncludedServices(includedPointer, includedService);
+
+ emit q->serviceDiscovered(includedUuid);
+ }
+}
+
+void QLowEnergyControllerPrivate::discoverServices()
+{
+ Q_Q(QLowEnergyController);
+
+ qCDebug(QT_BT_WINRT) << "Service discovery initiated";
+ ComPtr<IVectorView<GattDeviceService *>> deviceServices;
+ HRESULT hr = mDevice->get_GattServices(&deviceServices);
+ Q_ASSERT_SUCCEEDED(hr);
+ uint serviceCount;
+ hr = deviceServices->get_Size(&serviceCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (uint i = 0; i < serviceCount; ++i) {
+ ComPtr<IGattDeviceService> deviceService;
+ hr = deviceServices->GetAt(i, &deviceService);
+ Q_ASSERT_SUCCEEDED(hr);
+ GUID guuid;
+ hr = deviceService->get_Uuid(&guuid);
+ Q_ASSERT_SUCCEEDED(hr);
+ const QBluetoothUuid service(guuid);
+
+ QSharedPointer<QLowEnergyServicePrivate> pointer;
+ if (serviceList.contains(service)) {
+ pointer = serviceList.value(service);
+ } else {
+ QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate();
+ priv->uuid = service;
+ priv->setController(this);
+
+ pointer = QSharedPointer<QLowEnergyServicePrivate>(priv);
+ serviceList.insert(service, pointer);
+ }
+ pointer->type |= QLowEnergyService::PrimaryService;
+
+ obtainIncludedServices(pointer, deviceService);
+
+ emit q->serviceDiscovered(service);
+ }
+
+ setState(QLowEnergyController::DiscoveredState);
+ emit q->discoveryFinished();
+}
+
+void QLowEnergyControllerPrivate::discoverServiceDetails(const QBluetoothUuid &service)
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__ << service;
+ if (!serviceList.contains(service)) {
+ qCWarning(QT_BT_WINRT) << "Discovery done of unknown service:"
+ << service.toString();
+ return;
+ }
+
+ ComPtr<IGattDeviceService> deviceService = getNativeService(service);
+ if (!deviceService) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain native service for uuid " << service;
+ return;
+ }
+
+ //update service data
+ QSharedPointer<QLowEnergyServicePrivate> pointer = serviceList.value(service);
+
+ pointer->setState(QLowEnergyService::DiscoveringServices);
+ ComPtr<IGattDeviceService2> deviceService2;
+ HRESULT hr = deviceService.As(&deviceService2);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IVectorView<GattDeviceService *>> deviceServices;
+ hr = deviceService2->GetAllIncludedServices(&deviceServices);
+ Q_ASSERT_SUCCEEDED(hr);
+ uint serviceCount;
+ hr = deviceServices->get_Size(&serviceCount);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (uint i = 0; i < serviceCount; ++i) {
+ ComPtr<IGattDeviceService> includedService;
+ hr = deviceServices->GetAt(i, &includedService);
+ Q_ASSERT_SUCCEEDED(hr);
+ GUID guuid;
+ hr = includedService->get_Uuid(&guuid);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ const QBluetoothUuid service(guuid);
+ if (service.isNull()) {
+ qCDebug(QT_BT_WINRT) << "Could not find service";
+ return;
+ }
+
+ pointer->includedServices.append(service);
+
+ // update the type of the included service
+ QSharedPointer<QLowEnergyServicePrivate> otherService = serviceList.value(service);
+ if (!otherService.isNull())
+ otherService->type |= QLowEnergyService::IncludedService;
+ }
+
+ QWinRTLowEnergyServiceHandler *worker = new QWinRTLowEnergyServiceHandler(service, deviceService2);
+ QThread *thread = new QThread;
+ worker->moveToThread(thread);
+ connect(thread, &QThread::started, worker, &QWinRTLowEnergyServiceHandler::obtainCharList);
+ connect(thread, &QThread::finished, thread, &QObject::deleteLater);
+ connect(thread, &QThread::finished, worker, &QObject::deleteLater);
+ connect(worker, &QWinRTLowEnergyServiceHandler::charListObtained,
+ [this, thread](const QBluetoothUuid &service, QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList
+ , QVector<QBluetoothUuid> indicateChars
+ , QLowEnergyHandle startHandle, QLowEnergyHandle endHandle) {
+ if (!serviceList.contains(service)) {
+ qCWarning(QT_BT_WINRT) << "Discovery done of unknown service:"
+ << service.toString();
+ return;
+ }
+
+ QSharedPointer<QLowEnergyServicePrivate> pointer = serviceList.value(service);
+ pointer->startHandle = startHandle;
+ pointer->endHandle = endHandle;
+ pointer->characteristicList = charList;
+
+ HRESULT hr;
+ hr = QEventDispatcherWinRT::runOnXamlThread([indicateChars, service, this]() {
+ for (const QBluetoothUuid &indicateChar : indicateChars)
+ registerForValueChanges(service, indicateChar);
+ return S_OK;
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+
+ pointer->setState(QLowEnergyService::ServiceDiscovered);
+ thread->exit(0);
+ });
+ thread->start();
+}
+
+void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters &, const QLowEnergyAdvertisingData &, const QLowEnergyAdvertisingData &)
+{
+ Q_UNIMPLEMENTED();
+}
+
+void QLowEnergyControllerPrivate::stopAdvertising()
+{
+ Q_UNIMPLEMENTED();
+}
+
+void QLowEnergyControllerPrivate::requestConnectionUpdate(const QLowEnergyConnectionParameters &)
+{
+ Q_UNIMPLEMENTED();
+}
+
+void QLowEnergyControllerPrivate::readCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle)
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle;
+ Q_ASSERT(!service.isNull());
+ if (!service->characteristicList.contains(charHandle)) {
+ qCDebug(QT_BT_WINRT) << charHandle << "could not be found in service" << service->uuid;
+ service->setError(QLowEnergyService::CharacteristicReadError);
+ return;
+ }
+
+ HRESULT hr;
+ hr = QEventDispatcherWinRT::runOnXamlThread([charHandle, service, this]() {
+ const QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle);
+ if (!(charData.properties & QLowEnergyCharacteristic::Read))
+ qCDebug(QT_BT_WINRT) << "Read flag is not set for characteristic" << charData.uuid;
+
+ ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(service->uuid, charData.uuid);
+ if (!characteristic) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain native characteristic" << charData.uuid
+ << "from service" << service->uuid;
+ service->setError(QLowEnergyService::CharacteristicReadError);
+ return S_OK;
+ }
+ ComPtr<IAsyncOperation<GattReadResult*>> readOp;
+ HRESULT hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ auto readCompletedLambda = [charData, charHandle, service]
+ (IAsyncOperation<GattReadResult*> *op, AsyncStatus status)
+ {
+ if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
+ qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "read operation failed.";
+ service->setError(QLowEnergyService::CharacteristicReadError);
+ return S_OK;
+ }
+ ComPtr<IGattReadResult> characteristicValue;
+ HRESULT hr;
+ hr = op->GetResults(&characteristicValue);
+ if (FAILED(hr)) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain result for characteristic" << charHandle;
+ service->setError(QLowEnergyService::CharacteristicReadError);
+ return S_OK;
+ }
+
+ const QByteArray value = byteArrayFromGattResult(characteristicValue);
+ QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle);
+ charData.value = value;
+ service->characteristicList.insert(charHandle, charData);
+ emit service->characteristicRead(QLowEnergyCharacteristic(service, charHandle), value);
+ return S_OK;
+ };
+ hr = readOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattReadResult *>>(readCompletedLambda).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ return S_OK;
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+}
+
+void QLowEnergyControllerPrivate::readDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descHandle)
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle;
+ Q_ASSERT(!service.isNull());
+ if (!service->characteristicList.contains(charHandle)) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "in characteristic" << charHandle
+ << "cannot be found in service" << service->uuid;
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return;
+ }
+
+ HRESULT hr;
+ hr = QEventDispatcherWinRT::runOnXamlThread([charHandle, descHandle, service, this]() {
+ QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle);
+ ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(service->uuid, charData.uuid);
+ if (!characteristic) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain native characteristic" << charData.uuid
+ << "from service" << service->uuid;
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return S_OK;
+ }
+
+ // Get native descriptor
+ if (!charData.descriptorList.contains(descHandle))
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "cannot be found in characteristic" << charHandle;
+ QLowEnergyServicePrivate::DescData descData = charData.descriptorList.value(descHandle);
+ if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) {
+ ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp;
+ HRESULT hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ auto readCompletedLambda = [&charData, charHandle, &descData, descHandle, service]
+ (IAsyncOperation<ClientCharConfigDescriptorResult *> *op, AsyncStatus status)
+ {
+ if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "read operation failed";
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return S_OK;
+ }
+ ComPtr<IClientCharConfigDescriptorResult> iValue;
+ HRESULT hr;
+ hr = op->GetResults(&iValue);
+ if (FAILED(hr)) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle;
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return S_OK;
+ }
+ GattClientCharacteristicConfigurationDescriptorValue value;
+ hr = iValue->get_ClientCharacteristicConfigurationDescriptor(&value);
+ if (FAILED(hr)) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain value for descriptor" << descHandle;
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return S_OK;
+ }
+ quint16 result = 0;
+ bool correct = false;
+ if (value & GattClientCharacteristicConfigurationDescriptorValue_Indicate) {
+ result |= QLowEnergyCharacteristic::Indicate;
+ correct = true;
+ }
+ if (value & GattClientCharacteristicConfigurationDescriptorValue_Notify) {
+ result |= QLowEnergyCharacteristic::Notify;
+ correct = true;
+ }
+ if (value == GattClientCharacteristicConfigurationDescriptorValue_None)
+ correct = true;
+ if (!correct) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle
+ << "read operation failed. Obtained unexpected value.";
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return S_OK;
+ }
+ descData.value = QByteArray(2, Qt::Uninitialized);
+ qToLittleEndian(result, descData.value.data());
+ charData.descriptorList.insert(descHandle, descData);
+ emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle),
+ descData.value);
+ return S_OK;
+ };
+ hr = readOp->put_Completed(Callback<IAsyncOperationCompletedHandler<ClientCharConfigDescriptorResult *>>(readCompletedLambda).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ return S_OK;
+ } else {
+ ComPtr<IVectorView<GattDescriptor *>> descriptors;
+ HRESULT hr = characteristic->GetDescriptors(descData.uuid, &descriptors);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IGattDescriptor> descriptor;
+ hr = descriptors->GetAt(0, &descriptor);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IAsyncOperation<GattReadResult*>> readOp;
+ hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ auto readCompletedLambda = [&charData, charHandle, &descData, descHandle, service]
+ (IAsyncOperation<GattReadResult*> *op, AsyncStatus status)
+ {
+ if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "read operation failed";
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return S_OK;
+ }
+ ComPtr<IGattReadResult> descriptorValue;
+ HRESULT hr;
+ hr = op->GetResults(&descriptorValue);
+ if (FAILED(hr)) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle;
+ service->setError(QLowEnergyService::DescriptorReadError);
+ return S_OK;
+ }
+ if (descData.uuid == QBluetoothUuid::CharacteristicUserDescription)
+ descData.value = byteArrayFromGattResult(descriptorValue, true);
+ else
+ descData.value = byteArrayFromGattResult(descriptorValue);
+ charData.descriptorList.insert(descHandle, descData);
+ emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle),
+ descData.value);
+ return S_OK;
+ };
+ hr = readOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattReadResult *>>(readCompletedLambda).Get());
+ return S_OK;
+ }
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+}
+
+void QLowEnergyControllerPrivate::writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle,
+ const QByteArray &newValue,
+ QLowEnergyService::WriteMode mode)
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << newValue << mode;
+ Q_ASSERT(!service.isNull());
+ if (!service->characteristicList.contains(charHandle)) {
+ qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "cannot be found in service" << service->uuid;
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return;
+ }
+
+ QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle);
+ const bool writeWithResponse = mode == QLowEnergyService::WriteWithResponse;
+ if (!(charData.properties & (writeWithResponse ? QLowEnergyCharacteristic::Write : QLowEnergyCharacteristic::WriteNoResponse)))
+ qCDebug(QT_BT_WINRT) << "Write flag is not set for characteristic" << charHandle;
+
+ HRESULT hr;
+ hr = QEventDispatcherWinRT::runOnXamlThread([charData, charHandle, this, service, newValue, writeWithResponse]() {
+ ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(service->uuid, charData.uuid);
+ if (!characteristic) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain native characteristic" << charData.uuid
+ << "from service" << service->uuid;
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return S_OK;
+ }
+ ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory;
+ HRESULT hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(), &bufferFactory);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
+ const int length = newValue.length();
+ hr = bufferFactory->Create(length, &buffer);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = buffer->put_Length(length);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess;
+ hr = buffer.As(&byteAccess);
+ Q_ASSERT_SUCCEEDED(hr);
+ byte *bytes;
+ hr = byteAccess->Buffer(&bytes);
+ Q_ASSERT_SUCCEEDED(hr);
+ memcpy(bytes, newValue, length);
+ ComPtr<IAsyncOperation<GattCommunicationStatus>> writeOp;
+ GattWriteOption option = writeWithResponse ? GattWriteOption_WriteWithResponse : GattWriteOption_WriteWithoutResponse;
+ hr = characteristic->WriteValueWithOptionAsync(buffer.Get(), option, &writeOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ auto writeCompletedLambda =[charData, charHandle, newValue, service, this]
+ (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status)
+ {
+ if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
+ qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "write operation failed";
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return S_OK;
+ }
+ GattCommunicationStatus result;
+ HRESULT hr;
+ hr = op->GetResults(&result);
+ if (hr == E_BLUETOOTH_ATT_INVALID_ATTRIBUTE_VALUE_LENGTH) {
+ qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "write operation was tried with invalid value length";
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return S_OK;
+ }
+ Q_ASSERT_SUCCEEDED(hr);
+ if (result != GattCommunicationStatus_Success) {
+ qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "write operation failed";
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ return S_OK;
+ }
+ // only update cache when property is readable. Otherwise it remains
+ // empty.
+ if (charData.properties & QLowEnergyCharacteristic::Read)
+ updateValueOfCharacteristic(charHandle, newValue, false);
+ emit service->characteristicWritten(QLowEnergyCharacteristic(service, charHandle), newValue);
+ return S_OK;
+ };
+ hr = writeOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus>>(writeCompletedLambda).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ return S_OK;
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+}
+
+void QLowEnergyControllerPrivate::writeDescriptor(
+ const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle,
+ const QLowEnergyHandle descHandle,
+ const QByteArray &newValue)
+{
+ qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle << newValue;
+ Q_ASSERT(!service.isNull());
+ if (!service->characteristicList.contains(charHandle)) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "in characteristic" << charHandle
+ << "could not be found in service" << service->uuid;
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return;
+ }
+
+ HRESULT hr;
+ hr = QEventDispatcherWinRT::runOnXamlThread([charHandle, descHandle, this, service, newValue]() {
+ const QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle);
+ ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(service->uuid, charData.uuid);
+ if (!characteristic) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain native characteristic" << charData.uuid
+ << "from service" << service->uuid;
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+
+ // Get native descriptor
+ if (!charData.descriptorList.contains(descHandle))
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "could not be found in Characteristic" << charHandle;
+
+ QLowEnergyServicePrivate::DescData descData = charData.descriptorList.value(descHandle);
+ if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) {
+ GattClientCharacteristicConfigurationDescriptorValue value;
+ quint16 intValue = qFromLittleEndian<quint16>(newValue);
+ if (intValue & GattClientCharacteristicConfigurationDescriptorValue_Indicate && intValue & GattClientCharacteristicConfigurationDescriptorValue_Notify) {
+ qCWarning(QT_BT_WINRT) << "Setting both Indicate and Notify is not supported on WinRT";
+ value = (GattClientCharacteristicConfigurationDescriptorValue)(GattClientCharacteristicConfigurationDescriptorValue_Indicate | GattClientCharacteristicConfigurationDescriptorValue_Notify);
+ } else if (intValue & GattClientCharacteristicConfigurationDescriptorValue_Indicate) {
+ value = GattClientCharacteristicConfigurationDescriptorValue_Indicate;
+ } else if (intValue & GattClientCharacteristicConfigurationDescriptorValue_Notify) {
+ value = GattClientCharacteristicConfigurationDescriptorValue_Notify;
+ } else if (intValue == 0) {
+ value = GattClientCharacteristicConfigurationDescriptorValue_None;
+ } else {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed: Invalid value";
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+ ComPtr<IAsyncOperation<enum GattCommunicationStatus>> writeOp;
+ HRESULT hr = characteristic->WriteClientCharacteristicConfigurationDescriptorAsync(value, &writeOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ auto writeCompletedLambda = [charHandle, descHandle, newValue, service, this]
+ (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status)
+ {
+ if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed";
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+ GattCommunicationStatus result;
+ HRESULT hr;
+ hr = op->GetResults(&result);
+ if (FAILED(hr)) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle;
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+ if (result != GattCommunicationStatus_Success) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed";
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+ updateValueOfDescriptor(charHandle, descHandle, newValue, false);
+ emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle), newValue);
+ return S_OK;
+ };
+ hr = writeOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus >>(writeCompletedLambda).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ } else {
+ ComPtr<IVectorView<GattDescriptor *>> descriptors;
+ HRESULT hr = characteristic->GetDescriptors(descData.uuid, &descriptors);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IGattDescriptor> descriptor;
+ hr = descriptors->GetAt(0, &descriptor);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory;
+ hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(), &bufferFactory);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
+ const int length = newValue.length();
+ hr = bufferFactory->Create(length, &buffer);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = buffer->put_Length(length);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess;
+ hr = buffer.As(&byteAccess);
+ Q_ASSERT_SUCCEEDED(hr);
+ byte *bytes;
+ hr = byteAccess->Buffer(&bytes);
+ Q_ASSERT_SUCCEEDED(hr);
+ memcpy(bytes, newValue, length);
+ ComPtr<IAsyncOperation<GattCommunicationStatus>> writeOp;
+ hr = descriptor->WriteValueAsync(buffer.Get(), &writeOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ auto writeCompletedLambda = [charHandle, descHandle, newValue, service, this]
+ (IAsyncOperation<GattCommunicationStatus> *op, AsyncStatus status)
+ {
+ if (status == AsyncStatus::Canceled || status == AsyncStatus::Error) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed";
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+ GattCommunicationStatus result;
+ HRESULT hr;
+ hr = op->GetResults(&result);
+ if (FAILED(hr)) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle;
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+ if (result != GattCommunicationStatus_Success) {
+ qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed";
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ return S_OK;
+ }
+ updateValueOfDescriptor(charHandle, descHandle, newValue, false);
+ emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle), newValue);
+ return S_OK;
+ };
+ hr = writeOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus>>(writeCompletedLambda).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ return S_OK;
+ }
+ return S_OK;
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+}
+
+
+void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServiceData &, QLowEnergyHandle)
+{
+ Q_UNIMPLEMENTED();
+}
+
+void QLowEnergyControllerPrivate::characteristicChanged(
+ int charHandle, const QByteArray &data)
+{
+ QSharedPointer<QLowEnergyServicePrivate> service =
+ serviceForHandle(charHandle);
+ if (service.isNull())
+ return;
+
+ qCDebug(QT_BT_WINRT) << "Characteristic change notification" << service->uuid
+ << charHandle << data.toHex();
+
+ QLowEnergyCharacteristic characteristic = characteristicForHandle(charHandle);
+ if (!characteristic.isValid()) {
+ qCWarning(QT_BT_WINRT) << "characteristicChanged: Cannot find characteristic";
+ return;
+ }
+
+ // only update cache when property is readable. Otherwise it remains
+ // empty.
+ if (characteristic.properties() & QLowEnergyCharacteristic::Read)
+ updateValueOfCharacteristic(characteristic.attributeHandle(),
+ data, false);
+ emit service->characteristicChanged(characteristic, data);
+}
+
+QT_END_NAMESPACE
+
+#include "qlowenergycontroller_winrt.moc"
diff --git a/src/bluetooth/qlowenergyservice.cpp b/src/bluetooth/qlowenergyservice.cpp
index e9722f81..6e33c565 100644
--- a/src/bluetooth/qlowenergyservice.cpp
+++ b/src/bluetooth/qlowenergyservice.cpp
@@ -294,6 +294,8 @@ QT_BEGIN_NAMESPACE
the read operation is not successful, the \l error() signal is emitted using the
\l CharacteristicReadError flag.
+ \note This signal is only emitted for Central Role related use cases.
+
\sa readCharacteristic()
\since 5.5
*/
@@ -306,18 +308,14 @@ QT_BEGIN_NAMESPACE
by calling \l writeCharacteristic(). If the write operation is not successful,
the \l error() signal is emitted using the \l CharacteristicWriteError flag.
- Since this signal is an indication of a successful write operation \a newValue
- generally matches the value that was passed to the associated
- \l writeCharacteristic() call. However, it may happen that the two values differ
- from each other. This can occur in cases when the written value is
- used by the remote device to trigger an operation and it returns some other value via
- the written and/or change notification. Such cases are very specific to the
- target device. In any case, the reception of the written signal can still be considered
- as a sign that the target device received the to-be-written value.
+ The reception of the written signal can be considered as a sign that the target device
+ received the to-be-written value and reports back the status of write request.
\note If \l writeCharacteristic() is called using the \l WriteWithoutResponse mode,
this signal and the \l error() are never emitted.
+ \note This signal is only emitted for Central Role related use cases.
+
\sa writeCharacteristic()
*/
@@ -326,7 +324,7 @@ QT_BEGIN_NAMESPACE
If the associated controller object is in the \l {QLowEnergyController::CentralRole}{central}
role, this signal is emitted when the value of \a characteristic is changed by an event on the
- peripheral. In that case, the signal emission implies that change notifications must
+ peripheral/device side. In that case, the signal emission implies that change notifications must
have been activated via the characteristic's
\l {QBluetoothUuid::ClientCharacteristicConfiguration}{ClientCharacteristicConfiguration}
descriptor prior to the change event on the peripheral. More details on how this might be
@@ -348,6 +346,8 @@ QT_BEGIN_NAMESPACE
the read operation is not successful, the \l error() signal is emitted using the
\l DescriptorReadError flag.
+ \note This signal is only emitted for Central Role related use cases.
+
\sa readDescriptor()
\since 5.5
*/
@@ -648,7 +648,8 @@ void QLowEnergyService::readCharacteristic(
The call results in a write request or command to a remote peripheral.
If the operation is successful,
the \l characteristicWritten() signal is emitted; otherwise the \l CharacteristicWriteError
- is set.
+ is set. Calling this function does not trigger the a \l characteristicChanged()
+ signal unless the peripheral itself changes the value again after the current write request.
The \a mode parameter determines whether the remote device should send a write
confirmation. The to-be-written \a characteristic must support the relevant
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;
}
diff --git a/src/imports/bluetooth/plugin.cpp b/src/imports/bluetooth/plugin.cpp
index eca5736e..f77a4d95 100644
--- a/src/imports/bluetooth/plugin.cpp
+++ b/src/imports/bluetooth/plugin.cpp
@@ -57,7 +57,7 @@ QT_USE_NAMESPACE
class QBluetoothQmlPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
- Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
QBluetoothQmlPlugin(QObject *parent = 0) : QQmlExtensionPlugin(parent) { initResources(); }
void registerTypes(const char *uri)
@@ -81,9 +81,9 @@ public:
qmlRegisterType<QDeclarativeBluetoothService >(uri, major, minor, "BluetoothService");
qmlRegisterType<QDeclarativeBluetoothSocket >(uri, major, minor, "BluetoothSocket");
- // Register the 5.7 types
- // introduces 5.7 version, other existing 5.2 exports become automatically available under 5.2-5.6
- minor = 7;
+ // Register the 5.8 types
+ // introduces 5.8 version, other existing 5.2 exports become automatically available under 5.2-5.7
+ minor = 8;
qmlRegisterType<QDeclarativeBluetoothDiscoveryModel >(uri, major, minor, "BluetoothDiscoveryModel");
}
};
diff --git a/src/imports/bluetooth/plugins.qmltypes b/src/imports/bluetooth/plugins.qmltypes
index 9c47126d..0ffe3223 100644
--- a/src/imports/bluetooth/plugins.qmltypes
+++ b/src/imports/bluetooth/plugins.qmltypes
@@ -4,17 +4,17 @@ import QtQuick.tooling 1.2
// It is used for QML tooling purposes only.
//
// This file was auto-generated by:
-// 'qmlplugindump -nonrelocatable QtBluetooth 5.7'
+// 'qmlplugindump -nonrelocatable QtBluetooth 5.8'
Module {
- dependencies: ["QtQuick 2.0"]
+ dependencies: ["QtQuick 2.8"]
Component {
name: "QDeclarativeBluetoothDiscoveryModel"
prototype: "QAbstractListModel"
exports: [
"QtBluetooth/BluetoothDiscoveryModel 5.0",
"QtBluetooth/BluetoothDiscoveryModel 5.2",
- "QtBluetooth/BluetoothDiscoveryModel 5.7"
+ "QtBluetooth/BluetoothDiscoveryModel 5.8"
]
exportMetaObjectRevisions: [0, 0, 0]
Enum {
diff --git a/src/imports/nfc/plugin.cpp b/src/imports/nfc/plugin.cpp
index 5f2ccd26..d003f531 100644
--- a/src/imports/nfc/plugin.cpp
+++ b/src/imports/nfc/plugin.cpp
@@ -59,7 +59,7 @@ QT_USE_NAMESPACE
class QNfcQmlPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
- Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
QNfcQmlPlugin(QObject *parent = 0) : QQmlExtensionPlugin(parent) { initResources(); }
@@ -98,8 +98,8 @@ public:
minor = 5;
qmlRegisterType<QDeclarativeNearField, 1>(uri, major, minor, "NearField");
- // Register the 5.6 - 5.7 types
- minor = 7;
+ // Register the 5.6 - 5.8 types
+ minor = 8;
qmlRegisterType<QDeclarativeNearField, 1>(uri, major, minor, "NearField");
}
diff --git a/src/imports/nfc/plugins.qmltypes b/src/imports/nfc/plugins.qmltypes
index 7bba6d95..c9fae196 100644
--- a/src/imports/nfc/plugins.qmltypes
+++ b/src/imports/nfc/plugins.qmltypes
@@ -4,10 +4,10 @@ import QtQuick.tooling 1.2
// It is used for QML tooling purposes only.
//
// This file was auto-generated by:
-// 'qmlplugindump -nonrelocatable QtNfc 5.7'
+// 'qmlplugindump -nonrelocatable QtNfc 5.8'
Module {
- dependencies: ["QtQuick 2.0"]
+ dependencies: ["QtQuick 2.8"]
Component {
name: "QDeclarativeNdefFilter"
prototype: "QObject"
@@ -58,7 +58,7 @@ Module {
"QtNfc/NearField 5.2",
"QtNfc/NearField 5.4",
"QtNfc/NearField 5.5",
- "QtNfc/NearField 5.7"
+ "QtNfc/NearField 5.8"
]
exportMetaObjectRevisions: [0, 0, 0, 1, 1]
Property { name: "messageRecords"; type: "QQmlNdefRecord"; isList: true; isReadonly: true }
diff --git a/src/src.pro b/src/src.pro
index 97b32522..38be9a4f 100644
--- a/src/src.pro
+++ b/src/src.pro
@@ -21,6 +21,10 @@ qtHaveModule(quick) {
SUBDIRS += imports
}
-config_bluez:qtHaveModule(dbus) {
- SUBDIRS += tools/sdpscanner
+include($$OUT_PWD/bluetooth/qtbluetooth-config.pri)
+QT_FOR_CONFIG += bluetooth-private
+qtConfig(bluez):qtHaveModule(dbus) {
+ sdpscanner.subdir = tools/sdpscanner
+ sdpscanner.depends = bluetooth
+ SUBDIRS += sdpscanner
}
diff --git a/src/tools/sdpscanner/qt_attribution.json b/src/tools/sdpscanner/qt_attribution.json
new file mode 100644
index 00000000..9f2ada92
--- /dev/null
+++ b/src/tools/sdpscanner/qt_attribution.json
@@ -0,0 +1,12 @@
+{
+ "Id": "bluez",
+ "Name": "BlueZ",
+ "QDocModule": "qtbluetooth",
+ "QtUsage": "On Linux, Qt Bluetooth uses a separate executable, sdpscanner, to integrate with the official Linux bluetooth protocol stack BlueZ. The Qt Bluetooth library does not link against BlueZ directly.",
+
+ "Description": "BlueZ",
+ "Homepage": "http://www.bluez.org/",
+ "LicenseId": "GPL-2.0",
+ "License": "GNU General Public License v2.0 only",
+ "Copyright": "Copyright (C) 2000-2016 BlueZ Project."
+}
diff --git a/src/tools/sdpscanner/sdpscanner.pro b/src/tools/sdpscanner/sdpscanner.pro
index e4773cae..78610ebd 100644
--- a/src/tools/sdpscanner/sdpscanner.pro
+++ b/src/tools/sdpscanner/sdpscanner.pro
@@ -5,8 +5,8 @@ QT = core
SOURCES = main.cpp
-CONFIG += link_pkgconfig
-PKGCONFIG_PRIVATE += bluez
+QT_FOR_CONFIG += bluetooth-private
+QMAKE_USE += bluez
load(qt_tool)
diff --git a/sync.profile b/sync.profile
index d193d5ac..64dac6e3 100644
--- a/sync.profile
+++ b/sync.profile
@@ -4,3 +4,15 @@
);
%moduleheaders = ( # restrict the module headers to those found in relative path
);
+
+@ignore_for_include_check = (
+
+ # OBEX auto-generated headers
+ "adapter1_bluez5_p.h", "adapter_p.h", "agent_p.h", "device1_bluez5_p.h",
+ "device_p.h", "manager_p.h", "obex_agent_p.h", "obex_client1_bluez5_p.h",
+ "obex_client_p.h", "obex_manager_p.h", "obex_objectpush1_bluez5_p.h",
+ "obex_transfer1_bluez5_p.h", "obex_transfer_p.h", "objectmanager_p.h",
+ "profile1_p.h", "properties_p.h", "service_p.h",
+ # NFC auto-generated headers
+ # Note: "adapter_p.h", "agent_p.h" and "manager_p.h" are duplicated here
+ "dbusobjectmanager_p.h", "dbusproperties_p.h", "tag_p.h");
diff --git a/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro b/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro
index 36c88cdc..96880930 100644
--- a/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro
+++ b/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro
@@ -2,5 +2,9 @@ SOURCES += tst_qbluetoothdevicediscoveryagent.cpp
TARGET=tst_qbluetoothdevicediscoveryagent
CONFIG += testcase
-QT = core concurrent bluetooth testlib
+QT = core concurrent bluetooth-private testlib
osx:QT += widgets
+
+qtConfig(bluez):qtHaveModule(dbus) {
+ DEFINES += QT_BLUEZ_BLUETOOTH
+}
diff --git a/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp b/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp
index 192173c8..efc4d8a6 100644
--- a/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp
+++ b/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp
@@ -77,8 +77,13 @@ private slots:
void tst_deviceDiscovery_data();
void tst_deviceDiscovery();
+
+ void tst_discoveryTimeout();
+
+ void tst_discoveryMethods();
private:
int noOfLocalDevices;
+ bool isBluez5Runtime = false;
};
tst_QBluetoothDeviceDiscoveryAgent::tst_QBluetoothDeviceDiscoveryAgent()
@@ -91,12 +96,59 @@ tst_QBluetoothDeviceDiscoveryAgent::~tst_QBluetoothDeviceDiscoveryAgent()
{
}
+#ifdef QT_BLUEZ_BLUETOOTH
+// This section was adopted from tst_qloggingcategory.cpp
+QString logMessage;
+
+QByteArray qMyMessageFormatString(QtMsgType type, const QMessageLogContext &context,
+ const QString &str)
+{
+ QByteArray message;
+ message.append(context.category);
+ switch (type) {
+ case QtDebugMsg: message.append(".debug"); break;
+ case QtInfoMsg: message.append(".info"); break;
+ case QtWarningMsg: message.append(".warning"); break;
+ case QtCriticalMsg:message.append(".critical"); break;
+ case QtFatalMsg: message.append(".fatal"); break;
+ }
+ message.append(": ");
+ message.append(qPrintable(str));
+
+ return message.simplified();
+}
+
+static void myCustomMessageHandler(QtMsgType type,
+ const QMessageLogContext &context,
+ const QString &msg)
+{
+ logMessage = qMyMessageFormatString(type, context, msg);
+}
+#endif
+
+
+
void tst_QBluetoothDeviceDiscoveryAgent::initTestCase()
{
qRegisterMetaType<QBluetoothDeviceInfo>();
qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::InquiryType>();
+#ifdef QT_BLUEZ_BLUETOOTH
+ // To distinguish Bluez 4 and 5 we peek into the debug output
+ // of first Bluetooth ctor. It executes a runtime test and prints the result
+ // as logging output. This avoids more complex runtime detection logic within this unit test.
+ QtMessageHandler oldMessageHandler;
+ oldMessageHandler = qInstallMessageHandler(myCustomMessageHandler);
+
+ noOfLocalDevices = QBluetoothLocalDevice::allDevices().count();
+ qInstallMessageHandler(oldMessageHandler);
+ isBluez5Runtime = logMessage.contains(QStringLiteral("Bluez 5"));
+ if (isBluez5Runtime)
+ qDebug() << "BlueZ 5 runtime detected.";
+#else
noOfLocalDevices = QBluetoothLocalDevice::allDevices().count();
+#endif
+
if (!noOfLocalDevices)
return;
@@ -419,10 +471,10 @@ void tst_QBluetoothDeviceDiscoveryAgent::tst_deviceDiscovery()
}
}
}
-#ifdef Q_OS_IOS
- //On iOS, we do not have access to the local device/adapter, numberOfAdapters is 0,
+#if defined(Q_OS_IOS) || defined(Q_OS_WINRT)
+ //On iOS/WinRT, we do not have access to the local device/adapter, numberOfAdapters is 0,
//so we skip this test at all.
- QSKIP("iOS: no local Bluetooth device available. Skipping remaining part of test.");
+ QSKIP("iOS/WinRT: no local Bluetooth device available. Skipping remaining part of test.");
#endif
//For multiple Bluetooth adapter do the check only for GeneralUnlimitedInquiry.
@@ -431,6 +483,165 @@ void tst_QBluetoothDeviceDiscoveryAgent::tst_deviceDiscovery()
}
}
+
+void tst_QBluetoothDeviceDiscoveryAgent::tst_discoveryTimeout()
+{
+ QBluetoothDeviceDiscoveryAgent agent;
+
+ // check default values
+#if defined(Q_OS_OSX) || defined(Q_OS_IOS) || defined(Q_OS_ANDROID) || defined(Q_OS_WINRT)
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000);
+ agent.setLowEnergyDiscoveryTimeout(-1); // negative ignored
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000);
+ agent.setLowEnergyDiscoveryTimeout(20000);
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000);
+#elif defined(QT_BLUEZ_BLUETOOTH)
+ if (isBluez5Runtime) {
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000);
+ agent.setLowEnergyDiscoveryTimeout(-1); // negative ignored
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000);
+ agent.setLowEnergyDiscoveryTimeout(25000);
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000);
+ } else {
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1);
+ agent.setLowEnergyDiscoveryTimeout(20000); // feature not supported -> ignored
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1);
+ }
+#else
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1);
+ agent.setLowEnergyDiscoveryTimeout(20000); // feature not supported -> ignored
+ QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1);
+#endif
+}
+
+void tst_QBluetoothDeviceDiscoveryAgent::tst_discoveryMethods()
+{
+ const QBluetoothLocalDevice localDevice;
+ if (localDevice.allDevices().size() != 1) {
+ // On iOS it returns 0 but we still have working BT.
+#ifndef Q_OS_IOS
+ QSKIP("This test expects exactly one local device working");
+#endif
+ }
+
+ const QBluetoothDeviceDiscoveryAgent::DiscoveryMethods
+ supportedMethods = QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods();
+
+ QVERIFY(supportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod);
+
+ QBluetoothDeviceDiscoveryAgent::DiscoveryMethod
+ unsupportedMethods = QBluetoothDeviceDiscoveryAgent::NoMethod;
+ QBluetoothDeviceInfo::CoreConfiguration
+ expectedConfiguration = QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration;
+
+ if (supportedMethods == QBluetoothDeviceDiscoveryAgent::ClassicMethod) {
+ unsupportedMethods = QBluetoothDeviceDiscoveryAgent::LowEnergyMethod;
+ expectedConfiguration = QBluetoothDeviceInfo::BaseRateCoreConfiguration;
+ } else if (supportedMethods == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) {
+ unsupportedMethods = QBluetoothDeviceDiscoveryAgent::ClassicMethod;
+ expectedConfiguration = QBluetoothDeviceInfo::LowEnergyCoreConfiguration;
+ }
+
+ QBluetoothDeviceDiscoveryAgent agent;
+ QSignalSpy finishedSpy(&agent, SIGNAL(finished()));
+ QSignalSpy errorSpy(&agent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)));
+ QSignalSpy discoveredSpy(&agent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)));
+
+ // NoMethod - should just immediately return:
+ agent.start(QBluetoothDeviceDiscoveryAgent::NoMethod);
+ QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError);
+ QVERIFY(!agent.isActive());
+ QCOMPARE(finishedSpy.size(), 0);
+ QCOMPARE(errorSpy.size(), 0);
+ QCOMPARE(discoveredSpy.size(), 0);
+
+ if (unsupportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod) {
+ agent.start(unsupportedMethods);
+ QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
+ QVERIFY(!agent.isActive());
+ QVERIFY(finishedSpy.isEmpty());
+ QCOMPARE(errorSpy.size(), 1);
+ errorSpy.clear();
+ QVERIFY(discoveredSpy.isEmpty());
+ }
+
+ // Start discovery, probably both Classic and LE methods:
+ agent.start(supportedMethods);
+ QVERIFY(agent.isActive());
+ QVERIFY(errorSpy.isEmpty());
+
+
+#define RUN_DISCOVERY(maxTimeout, step, condition) \
+ for (int scanTime = maxTimeout; (condition) && scanTime > 0; scanTime -= step) \
+ QTest::qWait(step);
+
+ // Wait for up to MaxScanTime for the scan to finish
+ const int timeStep = 15000;
+ RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty())
+
+ QVERIFY(!agent.isActive());
+ QVERIFY(errorSpy.size() <= 1);
+
+ if (errorSpy.size()) {
+ // For example, old iOS device could report it supports LE method,
+ // but it actually does not.
+ QVERIFY(supportedMethods == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
+ QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
+ } else {
+ QVERIFY(finishedSpy.count() == 1);
+ QVERIFY(agent.error() == QBluetoothDeviceDiscoveryAgent::NoError);
+ QVERIFY(agent.errorString().isEmpty());
+
+ while (!discoveredSpy.isEmpty()) {
+ const QBluetoothDeviceInfo info =
+ qvariant_cast<QBluetoothDeviceInfo>(discoveredSpy.takeFirst().at(0));
+ QVERIFY(info.isValid());
+ QVERIFY(info.coreConfigurations() & expectedConfiguration);
+ }
+ }
+
+ if (unsupportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod)
+ return;
+
+ // Both methods were reported as supported. We already tested them
+ // above, now let's test first Classic then LE.
+ finishedSpy.clear();
+ errorSpy.clear();
+ discoveredSpy.clear();
+
+ agent.start(QBluetoothDeviceDiscoveryAgent::ClassicMethod);
+ QVERIFY(agent.isActive());
+ QVERIFY(errorSpy.isEmpty());
+ QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError);
+
+ RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty())
+
+ QVERIFY(!agent.isActive());
+ QVERIFY(errorSpy.isEmpty());
+ QCOMPARE(finishedSpy.size(), 1);
+
+ finishedSpy.clear();
+ discoveredSpy.clear();
+
+ agent.start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
+ QVERIFY(agent.isActive());
+ QVERIFY(errorSpy.isEmpty());
+
+ RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty() && errorSpy.isEmpty())
+
+ QVERIFY(!agent.isActive());
+ QVERIFY(errorSpy.size() <= 1);
+
+ if (errorSpy.size()) {
+ QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
+ qDebug() << "QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods is inaccurate"
+ " on your platform/with your device, LowEnergyMethod is not supported";
+ } else {
+ QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError);
+ QCOMPARE(finishedSpy.size(), 1);
+ }
+}
+
QTEST_MAIN(tst_QBluetoothDeviceDiscoveryAgent)
#include "tst_qbluetoothdevicediscoveryagent.moc"
diff --git a/tests/auto/qbluetoothsocket/qbluetoothsocket.pro b/tests/auto/qbluetoothsocket/qbluetoothsocket.pro
index 641ff6c7..8c9b2acb 100644
--- a/tests/auto/qbluetoothsocket/qbluetoothsocket.pro
+++ b/tests/auto/qbluetoothsocket/qbluetoothsocket.pro
@@ -3,7 +3,7 @@ TARGET = tst_qbluetoothsocket
CONFIG += testcase
testcase.timeout = 250 # this test is slow
-QT = core concurrent network bluetooth testlib
+QT = core concurrent network bluetooth-private testlib
osx:QT += widgets
OTHER_FILES += \
@@ -11,8 +11,8 @@ OTHER_FILES += \
osx {
DEFINES += QT_OSX_BLUETOOTH
-} else:android {
+} else: android {
DEFINES += QT_ANDROID_BLUETOOTH
-} config_bluez:qtHaveModule(dbus) {
+} else: qtConfig(bluez):qtHaveModule(dbus) {
DEFINES += QT_BLUEZ_BLUETOOTH
}
diff --git a/tests/auto/qlowenergycontroller-gattserver/test/test.pro b/tests/auto/qlowenergycontroller-gattserver/test/test.pro
index 45cff660..fc9c7a18 100644
--- a/tests/auto/qlowenergycontroller-gattserver/test/test.pro
+++ b/tests/auto/qlowenergycontroller-gattserver/test/test.pro
@@ -3,7 +3,7 @@ QT = core bluetooth bluetooth-private testlib
TARGET = tst_qlowenergycontroller-gattserver
CONFIG += testcase c++11
-config_linux_crypto_api:DEFINES += CONFIG_LINUX_CRYPTO_API
-config_bluez_le:DEFINES += CONFIG_BLUEZ_LE
+qtConfig(linux_crypto_api): DEFINES += CONFIG_LINUX_CRYPTO_API
+qtConfig(bluez_le): DEFINES += CONFIG_BLUEZ_LE
SOURCES += tst_qlowenergycontroller-gattserver.cpp
diff --git a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
index 773c673b..6475b8c4 100644
--- a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
+++ b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
@@ -120,7 +120,7 @@ tst_QLowEnergyController::~tst_QLowEnergyController()
void tst_QLowEnergyController::initTestCase()
{
-#ifndef Q_OS_MAC
+#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT)
if (remoteDevice.isNull()
|| QBluetoothLocalDevice::allDevices().isEmpty()) {
qWarning("No remote device or local adapter found.");
@@ -142,7 +142,7 @@ void tst_QLowEnergyController::initTestCase()
QVERIFY(finishedSpy.isEmpty());
bool deviceFound = false;
- devAgent->start();
+ devAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
QTRY_VERIFY_WITH_TIMEOUT(finishedSpy.count() > 0, 30000);
foreach (const QBluetoothDeviceInfo &info, devAgent->discoveredDevices()) {
#ifndef Q_OS_MAC
@@ -244,7 +244,7 @@ void tst_QLowEnergyController::tst_connect()
{
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
-#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) || defined(Q_OS_WINRT)
if (!remoteDeviceInfo.isValid())
#else
if (localAdapters.isEmpty() || !remoteDeviceInfo.isValid())
@@ -252,6 +252,7 @@ void tst_QLowEnergyController::tst_connect()
QSKIP("No local Bluetooth or remote BTLE device found. Skipping test.");
QLowEnergyController control(remoteDeviceInfo);
+ QCOMPARE(remoteDeviceInfo.deviceUuid(), control.remoteDeviceUuid());
QCOMPARE(control.role(), QLowEnergyController::CentralRole);
QSignalSpy connectedSpy(&control, SIGNAL(connected()));
QSignalSpy disconnectedSpy(&control, SIGNAL(disconnected()));
@@ -260,7 +261,7 @@ void tst_QLowEnergyController::tst_connect()
else
QCOMPARE(control.remoteName(), remoteDeviceInfo.name());
-#if !defined(Q_OS_IOS) && !defined(Q_OS_TVOS)
+#if !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(Q_OS_WINRT)
const QBluetoothAddress localAdapter = localAdapters.at(0).address();
QCOMPARE(control.localAddress(), localAdapter);
QVERIFY(!control.localAddress().isNull());
@@ -403,7 +404,7 @@ void tst_QLowEnergyController::tst_connect()
void tst_QLowEnergyController::tst_concurrentDiscovery()
{
-#ifndef Q_OS_MAC
+#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");
@@ -440,7 +441,7 @@ void tst_QLowEnergyController::tst_concurrentDiscovery()
30000);
}
-#ifdef Q_OS_ANDROID
+#if defined(Q_OS_ANDROID) || defined(Q_OS_WINRT)
QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
QCOMPARE(control2.state(), QLowEnergyController::ConnectedState);
control2.disconnectFromDevice();
@@ -1642,7 +1643,7 @@ void tst_QLowEnergyController::tst_defaultBehavior()
void tst_QLowEnergyController::tst_writeCharacteristic()
{
-#ifndef Q_OS_MAC
+#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");
@@ -1816,7 +1817,7 @@ void tst_QLowEnergyController::tst_writeCharacteristic()
void tst_QLowEnergyController::tst_readWriteDescriptor()
{
-#ifndef Q_OS_MAC
+#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");
@@ -2239,7 +2240,7 @@ void tst_QLowEnergyController::tst_customProgrammableDevice()
*/
void tst_QLowEnergyController::tst_errorCases()
{
-#ifndef Q_OS_MAC
+#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");
@@ -2461,7 +2462,7 @@ void tst_QLowEnergyController::tst_errorCases()
*/
void tst_QLowEnergyController::tst_writeCharacteristicNoResponse()
{
-#ifndef Q_OS_MAC
+#if !defined(Q_OS_MAC) && !defined(Q_OS_WINRT)
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty())
QSKIP("No local Bluetooth device found. Skipping test.");