Revision control
Copy as Markdown
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qlowenergycontroller_winrt_p.h"
#include "qbluetoothutils_winrt_p.h"
#include <QtBluetooth/qbluetoothlocaldevice.h>
#include <QtBluetooth/QLowEnergyCharacteristicData>
#include <QtBluetooth/QLowEnergyDescriptorData>
#include <QtBluetooth/private/qbluetoothutils_winrt_p.h>
#include <QtBluetooth/QLowEnergyService>
#include <QtCore/QtEndian>
#include <QtCore/QLoggingCategory>
#include <QtCore/private/qfunctions_winrt_p.h>
#include <QtCore/QDeadlineTimer>
#include <QtCore/qpointer.h>
#include <QtCore/QAbstractEventDispatcher>
#include <QtCore/QDeadlineTimer>
#include <functional>
#include <type_traits>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Foundation.Metadata.h>
#include <winrt/Windows.Devices.Enumeration.h>
#include <winrt/Windows.Devices.Bluetooth.h>
#include <winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h>
#include <winrt/Windows.Storage.Streams.h>
#include <windows.devices.bluetooth.h>
#include <windows.devices.bluetooth.genericattributeprofile.h>
using namespace winrt;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Windows::Devices;
using namespace winrt::Windows::Devices::Bluetooth;
using namespace winrt::Windows::Devices::Bluetooth::GenericAttributeProfile;
using namespace winrt::Windows::Devices::Enumeration;
using namespace winrt::Windows::Storage::Streams;
namespace abi
{
using BluetoothLEDevice = ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice;
using GattSession = ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattSession;
}
template<typename E,
std::enable_if_t<std::is_enum_v<E>, int> = 0>
inline constexpr std::underlying_type_t<E> bitwise_and(E x, E y)
{
return static_cast<std::underlying_type_t<E>>(x) & static_cast<std::underlying_type_t<E>>(y);
}
template<typename T, typename E,
std::enable_if_t<std::conjunction_v<std::is_integral<T>, std::is_enum<E>>, int> = 0>
inline constexpr T bitwise_and(T x, E y)
{
return x & static_cast<std::underlying_type_t<E>>(y);
}
template<typename E,
std::enable_if_t<std::is_enum_v<E>, int> = 0>
inline constexpr std::underlying_type_t<E> bitwise_or(E x, E y)
{
return static_cast<std::underlying_type_t<E>>(x) | static_cast<std::underlying_type_t<E>>(y);
}
template<typename T, typename E,
std::enable_if_t<std::conjunction_v<std::is_integral<T>, std::is_enum<E>>, int> = 0>
inline constexpr T bitwise_or(T x, E y)
{
return x | static_cast<std::underlying_type_t<E>>(y);
}
template<typename T, typename E,
std::enable_if_t<std::conjunction_v<std::is_integral<T>, std::is_enum<E>>, int> = 0>
inline constexpr T &bitwise_or_equal(T &a, const E &b)
{
a |= static_cast<std::underlying_type_t<E>>(b);
return a;
}
#define ENUM_BITWISE_OPS(E) \
inline constexpr std::underlying_type_t<E> operator&(E x, E y) { return bitwise_and<E>(x, y); } \
\
template<typename T> \
inline constexpr T operator&(T x, E y) { return bitwise_and<T, E>(x, y); } \
\
template<typename T> \
inline constexpr T operator&(E x, T y) { return bitwise_and<T, E>(y, x); } \
\
inline constexpr std::underlying_type_t<E> operator|(E x, E y) { return bitwise_or<E>(x, y); } \
\
template<typename T> \
inline constexpr T operator|(T x, E y) { return bitwise_or<T, E>(x, y); } \
\
template<typename T> \
inline constexpr T operator|(E x, T y) { return bitwise_or<T, E>(y, x); } \
\
template<typename T> \
inline constexpr T &operator |=(T &a, const E &b) { return bitwise_or_equal(a, b); }
QT_BEGIN_NAMESPACE
ENUM_BITWISE_OPS(GattClientCharacteristicConfigurationDescriptorValue)
typedef GattReadClientCharacteristicConfigurationDescriptorResult ClientCharConfigDescriptorResult;
using GlobalCondition = std::function<bool()>;
static constexpr bool never() { return false; }
template <typename T>
static T await(IAsyncOperation<T> asyncInfo, GlobalCondition canceled = never, int timeout = 5000)
{
QDeadlineTimer awaitTime(timeout);
auto *dispatch = QAbstractEventDispatcher::instance();
if (!dispatch)
dispatch = QCoreApplication::eventDispatcher();
do {
QThread::yieldCurrentThread();
if (asyncInfo.Status() != winrt::AsyncStatus::Started) {
check_hresult(asyncInfo.ErrorCode());
return asyncInfo.GetResults();
}
if (dispatch)
dispatch->processEvents(QEventLoop::AllEvents);
} while (!canceled() && !awaitTime.hasExpired());
asyncInfo.Cancel();
throw hresult_error(E_ABORT);
}
constexpr int timeout_infinity = -1;
template <typename T>
static inline T await_forever(IAsyncOperation<T> asyncInfo, GlobalCondition canceled = never)
{
return await(asyncInfo, canceled, timeout_infinity);
}
#define WARN_AND_CONTINUE(msg) \
{ \
qCWarning(QT_BT_WINDOWS) << msg; \
continue; \
}
#define DEC_CHAR_COUNT_AND_CONTINUE(msg) \
{ \
qCWarning(QT_BT_WINDOWS) << msg; \
--mCharacteristicsCountToBeDiscovered; \
continue; \
}
#define RETURN_SERVICE_ERROR(msg, service, error) \
{ \
qCDebug(QT_BT_WINDOWS) << msg; \
service->setError(error); \
return; \
}
#define RETURN_FALSE(msg) \
{ \
qErrnoWarning(msg); \
return false; \
}
#define RETURN_MSG(msg) \
{ \
qErrnoWarning(msg); \
return; \
}
Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS)
Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS_SERVICE_THREAD)
static constexpr qint64 kMaxConnectTimeout = 20000; // 20 sec
static QByteArray byteArrayFromBuffer(IBuffer buffer, bool isWCharString = false)
{
if (!buffer) {
qErrnoWarning("nullptr passed to byteArrayFromBuffer");
return QByteArray();
}
qsizetype size = SAFE(buffer.Length());
if (!size)
return QByteArray();
if (isWCharString) {
QString valueString = QString::fromUtf16(
reinterpret_cast<char16_t *>(buffer.data())).left(size / 2);
return valueString.toUtf8();
}
return QByteArray(reinterpret_cast<char *>(buffer.data()), size);
}
static QByteArray byteArrayFromGattResult(GattReadResult gattResult,
bool isWCharString = false)
{
auto buffer = SAFE(gattResult.Value());
if (!buffer) {
qCWarning(QT_BT_WINDOWS) << "Could not obtain buffer from GattReadResult";
return QByteArray();
}
return byteArrayFromBuffer(buffer, isWCharString);
}
class QWinRTLowEnergyServiceHandler : public QObject
{
Q_OBJECT
public:
QWinRTLowEnergyServiceHandler(const QBluetoothUuid &service,
GattDeviceService deviceService,
QLowEnergyService::DiscoveryMode mode)
: mService(service), mMode(mode), mDeviceService(deviceService)
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
}
~QWinRTLowEnergyServiceHandler()
{
if (mDeviceService && mAbortRequested)
TRY(mDeviceService.Close());
qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
}
public slots:
void obtainCharList()
{
auto exitCondition = [this]() { return mAbortRequested; };
mIndicateChars.clear();
qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
auto characteristicsResult
= SAFE(await(mDeviceService.GetCharacteristicsAsync(), exitCondition));
if (!characteristicsResult)
return emitErrorAndQuitThread(QLatin1String("Could not obtain char list"));
if (!SAFE(characteristicsResult.Status() == GattCommunicationStatus::Success))
return emitErrorAndQuitThread(QLatin1String("Could not obtain char list"));
auto characteristics = SAFE(characteristicsResult.Characteristics());
if (!characteristics)
return emitErrorAndQuitThread(QLatin1String("Could not obtain char list"));
uint characteristicsCount = 0;
if (!TRY(characteristicsCount = characteristics.Size()))
return emitErrorAndQuitThread(QLatin1String("Could not obtain char list"));
mCharacteristicsCountToBeDiscovered = characteristicsCount;
for (uint i = 0; !mAbortRequested && (i < characteristicsCount); ++i) {
auto characteristic = SAFE(characteristics.GetAt(i));
if (!characteristic) {
qCWarning(QT_BT_WINDOWS) << "Could not obtain characteristic at" << i;
--mCharacteristicsCountToBeDiscovered;
continue;
}
// For some strange reason, Windows doesn't discover descriptors of characteristics (if not paired).
// Qt API assumes that all characteristics and their descriptors are discovered in one go.
// So we start 'GetDescriptorsAsync' for each discovered characteristic and finish only
// when GetDescriptorsAsync for all characteristics return.
auto descResult = SAFE(await(characteristic.GetDescriptorsAsync(), exitCondition));
if (!descResult)
DEC_CHAR_COUNT_AND_CONTINUE("Could not obtain descriptor read result");
uint16_t handle = 0;
if (!TRY(handle = characteristic.AttributeHandle()))
DEC_CHAR_COUNT_AND_CONTINUE("Could not obtain characteristic's attribute handle");
QLowEnergyServicePrivate::CharData charData;
charData.valueHandle = handle + 1;
if (mStartHandle == 0 || mStartHandle > handle)
mStartHandle = handle;
if (mEndHandle == 0 || mEndHandle < handle)
mEndHandle = handle;
if (!TRY(charData.uuid = QBluetoothUuid(characteristic.Uuid())))
DEC_CHAR_COUNT_AND_CONTINUE("Could not read characteristic UUID");
GattCharacteristicProperties properties;
if (!TRY(properties = characteristic.CharacteristicProperties()))
DEC_CHAR_COUNT_AND_CONTINUE("Could not read characteristic properties");
charData.properties = QLowEnergyCharacteristic::PropertyTypes(static_cast<uint32_t>(properties) & 0xff);
if (charData.properties & QLowEnergyCharacteristic::Read
&& mMode == QLowEnergyService::FullDiscovery) {
GattReadResult readResult = nullptr;
if (!TRY(readResult = await(characteristic.ReadValueAsync(BluetoothCacheMode::Uncached), exitCondition)))
DEC_CHAR_COUNT_AND_CONTINUE("Could not read characteristic");
if (!readResult)
qCWarning(QT_BT_WINDOWS) << "Characteristic read result is null";
else
charData.value = byteArrayFromGattResult(readResult);
}
// Insert the characteristic before its descriptors are discovered. Descriptor
// discovery below can fail, but the characteristic itself was discovered and
// has to stay visible in the Qt API. All further updates go through charIt,
// which stays valid until the next insertion into mCharacteristicList, i.e.
// until the next iteration of this loop.
auto charIt = mCharacteristicList.insert(handle, charData);
if (!SAFE(descResult.Status() == GattCommunicationStatus::Success))
DEC_CHAR_COUNT_AND_CONTINUE("Descriptor operation failed");
auto descriptors = SAFE(descResult.Descriptors());
if (!descriptors)
DEC_CHAR_COUNT_AND_CONTINUE("Could not obtain list of descriptors");
uint descriptorCount;
if (!TRY(descriptorCount = descriptors.Size()))
DEC_CHAR_COUNT_AND_CONTINUE("Could not obtain list of descriptors' size");
for (uint j = 0; !mAbortRequested && (j < descriptorCount); ++j) {
QLowEnergyServicePrivate::DescData descData;
GattDescriptor descriptor = SAFE(descriptors.GetAt(j));
if (!descriptor)
WARN_AND_CONTINUE("Could not access descriptor");
quint16 descHandle;
if (!TRY(descHandle = descriptor.AttributeHandle()))
WARN_AND_CONTINUE("Could not get descriptor handle");
if (!TRY(descData.uuid = QBluetoothUuid(descriptor.Uuid())))
WARN_AND_CONTINUE("Could not get descriptor UUID");
// Same reasoning as for the characteristic above: reading the value can
// fail, but the descriptor was discovered and has to be reported. descIt
// stays valid until the next insertion into the descriptor list, i.e.
// until the next iteration of this loop.
auto descIt = charIt->descriptorList.insert(descHandle, descData);
if (descData.uuid == QBluetoothUuid(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration)) {
mIndicateChars << charIt->uuid;
if (mMode == QLowEnergyService::FullDiscovery) {
auto readResult = SAFE(await(characteristic.ReadClientCharacteristicConfigurationDescriptorAsync(), exitCondition));
if (!readResult)
WARN_AND_CONTINUE("Could not read descriptor value");
GattClientCharacteristicConfigurationDescriptorValue value;
if (!TRY(value = readResult.ClientCharacteristicConfigurationDescriptor()))
WARN_AND_CONTINUE("Could not get descriptor value from result");
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;
descIt->value = QByteArray(2, Qt::Uninitialized);
qToLittleEndian(result, descIt->value.data());
}
} else {
if (mMode == QLowEnergyService::FullDiscovery) {
auto readResult = SAFE(await(descriptor.ReadValueAsync(BluetoothCacheMode::Uncached), exitCondition));
if (!readResult)
WARN_AND_CONTINUE("Could not read descriptor value");
descIt->value = byteArrayFromGattResult(readResult);
}
}
}
--mCharacteristicsCountToBeDiscovered;
}
checkAllCharacteristicsDiscovered();
}
void setAbortRequested()
{
mAbortRequested = true;
}
private:
void checkAllCharacteristicsDiscovered();
void emitErrorAndQuitThread(const QString &error);
public:
QBluetoothUuid mService;
QLowEnergyService::DiscoveryMode mMode;
GattDeviceService mDeviceService;
QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> mCharacteristicList;
uint mCharacteristicsCountToBeDiscovered = 0;
quint16 mStartHandle = 0;
quint16 mEndHandle = 0;
QList<QBluetoothUuid> mIndicateChars;
bool mAbortRequested = false;
signals:
void charListObtained(const QBluetoothUuid &service,
QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList,
QList<QBluetoothUuid> indicateChars, QLowEnergyHandle startHandle,
QLowEnergyHandle endHandle);
void errorOccured(const QString &error);
};
void QWinRTLowEnergyServiceHandler::checkAllCharacteristicsDiscovered()
{
if (!mAbortRequested && (mCharacteristicsCountToBeDiscovered == 0)) {
emit charListObtained(mService, mCharacteristicList, mIndicateChars,
mStartHandle, mEndHandle);
}
QThread::currentThread()->quit();
}
void QWinRTLowEnergyServiceHandler::emitErrorAndQuitThread(const QString &error)
{
mAbortRequested = true; // so that the service is closed during cleanup
emit errorOccured(error);
QThread::currentThread()->quit();
}
class QWinRTLowEnergyConnectionHandler : public QObject
{
Q_OBJECT
public:
explicit QWinRTLowEnergyConnectionHandler(const QBluetoothAddress &address) : mAddress(address)
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
// This should be checked before the handler is created
Q_ASSERT(!mAddress.isNull());
}
~QWinRTLowEnergyConnectionHandler()
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
mDevice = nullptr;
mGattSession = nullptr;
// To close the COM library gracefully, each successful call to
// CoInitialize, including those that return S_FALSE, must be balanced
// by a corresponding call to CoUninitialize.
if (mComInitialized)
winrt::uninit_apartment();
}
public slots:
void connectToDevice();
void handleDeviceDisconnectRequest();
signals:
void deviceConnected(com_ptr<abi::BluetoothLEDevice> device, com_ptr<abi::GattSession> session);
void errorOccurred(const QString &error);
private:
void connectToPairedDevice();
void connectToUnpairedDevice();
void emitErrorAndQuitThread(const QString &error);
void emitErrorAndQuitThread(const char *error);
void emitConnectedAndQuitThread();
BluetoothLEDevice mDevice = nullptr;
GattSession mGattSession = nullptr;
const QBluetoothAddress mAddress;
bool mAbortConnection = false;
bool mComInitialized = false;
};
void QWinRTLowEnergyConnectionHandler::connectToDevice()
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
mComInitialized = TRY(winrt::init_apartment());
auto earlyExit = [this]() { return mAbortConnection; };
if (!(mDevice = SAFE(await(BluetoothLEDevice::FromBluetoothAddressAsync(mAddress.toUInt64()), earlyExit))))
return emitErrorAndQuitThread("Could not find LE device from address");
// get GattSession: 1. get device id
BluetoothDeviceId deviceId = SAFE(mDevice.BluetoothDeviceId());
if (!deviceId)
return emitErrorAndQuitThread("Could not get device id");
// get GattSession: 3. get session
if (!(mGattSession = SAFE(await(GattSession::FromDeviceIdAsync(deviceId), earlyExit))))
return emitErrorAndQuitThread("Could not get GattSession from id");
BluetoothConnectionStatus status;
if (!TRY(status = mDevice.ConnectionStatus()))
return emitErrorAndQuitThread("Could not get connection status");
if (status == BluetoothConnectionStatus::Connected)
return emitConnectedAndQuitThread();
QBluetoothLocalDevice localDevice;
QBluetoothLocalDevice::Pairing pairing = localDevice.pairingStatus(mAddress);
if (pairing == QBluetoothLocalDevice::Unpaired)
connectToUnpairedDevice();
else
connectToPairedDevice();
}
void QWinRTLowEnergyConnectionHandler::handleDeviceDisconnectRequest()
{
mAbortConnection = true;
// Disconnect from the QLowEnergyControllerPrivateWinRT, so that it does
// not get notifications. It's absolutely fine to keep doing smth in
// background, as multiple connections to the same device should be handled
// correctly by OS.
disconnect(this, &QWinRTLowEnergyConnectionHandler::deviceConnected, nullptr, nullptr);
disconnect(this, &QWinRTLowEnergyConnectionHandler::errorOccurred, nullptr, nullptr);
}
void QWinRTLowEnergyConnectionHandler::connectToPairedDevice()
{
auto earlyExit = [this]() { return mAbortConnection; };
QDeadlineTimer deadline(kMaxConnectTimeout);
while (!mAbortConnection && !deadline.hasExpired()) {
auto deviceServicesResult = SAFE(await(mDevice.GetGattServicesAsync(), earlyExit));
if (!deviceServicesResult)
return emitErrorAndQuitThread("Could not obtain services");
if (!SAFE(deviceServicesResult.Status() == GattCommunicationStatus::Success))
return emitErrorAndQuitThread("Service operation failed");
auto deviceServices = SAFE(deviceServicesResult.Services());
if (!deviceServices)
return emitErrorAndQuitThread("Could not obtain list of services");
uint serviceCount;
if (!TRY(serviceCount = deviceServices.Size()))
return emitErrorAndQuitThread("Could not obtain size of list of services");
if (serviceCount == 0)
return emitErrorAndQuitThread("Found devices without services");
// Windows 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) {
auto service = SAFE(deviceServices.GetAt(i));
if (!service)
return emitErrorAndQuitThread("Could not obtain service");
auto characteristicsResult = SAFE(await(service.GetCharacteristicsAsync(), earlyExit));
if (!characteristicsResult)
return emitErrorAndQuitThread("Could not obtain characteristic");
if (!SAFE(characteristicsResult.Status() == GattCommunicationStatus::Success)) {
qCWarning(QT_BT_WINDOWS) << "Characteristic operation failed";
break;
}
IVectorView<GattCharacteristic> characteristics = nullptr;
auto hr = HR(characteristics = characteristicsResult.Characteristics());
if (hr == E_ACCESSDENIED) {
// Everything will work as expected up until this point if the
// manifest capabilties for bluetooth LE are not set.
return emitErrorAndQuitThread("Could not obtain characteristic list. "
"Please check your manifest capabilities");
} else if (FAILED(hr) || !characteristics) {
return emitErrorAndQuitThread("Could not obtain characteristic list.");
}
uint characteristicsCount;
if (!TRY(characteristicsCount = characteristics.Size()))
return emitErrorAndQuitThread("Could not obtain size of characteristic list.");
for (uint j = 0; j < characteristicsCount; ++j) {
auto characteristic = SAFE(characteristics.GetAt(j));
if (!characteristic)
return emitErrorAndQuitThread("Could not get characteristic");
GattReadResult result = nullptr;
hr = HR(result = await(
characteristic.ReadValueAsync(BluetoothCacheMode::Uncached), earlyExit));
if (hr == E_ILLEGAL_METHOD_CALL) {
// E_ILLEGAL_METHOD_CALL will be the result for a device, that is not reachable at
// the moment. In this case we should jump back into the outer loop and keep trying.
break;
} else if (FAILED(hr) || !result) {
return emitErrorAndQuitThread("Could not read characteristic value");
}
auto buffer = SAFE(result.Value());
if (!buffer) {
qCDebug(QT_BT_WINDOWS) << "Problem reading value";
break;
}
emitConnectedAndQuitThread();
return;
}
}
}
// If we got here because of mAbortConnection == true, the error message
// will not be delivered, so it does not matter. But we need to terminate
// the thread anyway!
emitErrorAndQuitThread("Connect to device failed due to timeout!");
}
void QWinRTLowEnergyConnectionHandler::connectToUnpairedDevice()
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
auto earlyExit = [this]() { return mAbortConnection; };
QDeadlineTimer deadline(kMaxConnectTimeout);
while (!mAbortConnection && !deadline.hasExpired()) {
auto deviceServicesResult = SAFE(await_forever(mDevice.GetGattServicesAsync(), earlyExit));
if (!deviceServicesResult)
return emitErrorAndQuitThread("Could not obtain services");
GattCommunicationStatus commStatus;
if (!TRY(commStatus = deviceServicesResult.Status()))
return emitErrorAndQuitThread("Could not obtain comm status");
if (commStatus == GattCommunicationStatus::Unreachable)
continue;
if (commStatus != GattCommunicationStatus::Success)
return emitErrorAndQuitThread("Service operation failed");
emitConnectedAndQuitThread();
return;
}
// If we got here because of mAbortConnection == true, the error message
// will not be delivered, so it does not matter. But we need to terminate
// the thread anyway!
emitErrorAndQuitThread("Connect to device failed due to timeout!");
}
void QWinRTLowEnergyConnectionHandler::emitErrorAndQuitThread(const QString &error)
{
emit errorOccurred(error);
QThread::currentThread()->quit();
}
void QWinRTLowEnergyConnectionHandler::emitErrorAndQuitThread(const char *error)
{
emitErrorAndQuitThread(QString::fromUtf8(error));
}
void QWinRTLowEnergyConnectionHandler::emitConnectedAndQuitThread()
{
emit deviceConnected(mDevice.as<abi::BluetoothLEDevice>(), mGattSession.as<abi::GattSession>());
QThread::currentThread()->quit();
}
QLowEnergyControllerPrivateWinRT::QLowEnergyControllerPrivateWinRT()
: QLowEnergyControllerPrivate()
{
registerQLowEnergyControllerMetaType();
connect(this, &QLowEnergyControllerPrivateWinRT::characteristicChanged,
this, &QLowEnergyControllerPrivateWinRT::handleCharacteristicChanged,
Qt::QueuedConnection);
}
QLowEnergyControllerPrivateWinRT::~QLowEnergyControllerPrivateWinRT()
{
unregisterFromStatusChanges();
unregisterFromValueChanges();
}
void QLowEnergyControllerPrivateWinRT::init()
{
}
void QLowEnergyControllerPrivateWinRT::connectToDevice()
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
if (remoteDevice.isNull()) {
qCWarning(QT_BT_WINDOWS) << "Invalid/null remote device address";
setError(QLowEnergyController::UnknownRemoteDeviceError);
return;
}
setState(QLowEnergyController::ConnectingState);
QWinRTLowEnergyConnectionHandler *worker = new QWinRTLowEnergyConnectionHandler(remoteDevice);
QThread *thread = new QThread;
worker->moveToThread(thread);
connect(this, &QLowEnergyControllerPrivateWinRT::abortConnection, worker,
&QWinRTLowEnergyConnectionHandler::handleDeviceDisconnectRequest);
connect(thread, &QThread::started, worker, &QWinRTLowEnergyConnectionHandler::connectToDevice);
connect(thread, &QThread::finished, worker, &QObject::deleteLater);
connect(worker, &QObject::destroyed, thread, &QObject::deleteLater);
connect(worker, &QWinRTLowEnergyConnectionHandler::errorOccurred, this,
[this](const QString &msg) { handleConnectionError(msg.toUtf8().constData()); });
connect(worker, &QWinRTLowEnergyConnectionHandler::deviceConnected, this,
[this](com_ptr<abi::BluetoothLEDevice> device, com_ptr<abi::GattSession> session) {
if (!device || !session) {
handleConnectionError("Failed to get device or gatt service");
return;
}
mDevice = device.as<BluetoothLEDevice>();
mGattSession = session.as<GattSession>();
if (!registerForStatusChanges() || !registerForMtuChanges()) {
handleConnectionError("Failed to register for changes");
return;
}
Q_Q(QLowEnergyController);
setState(QLowEnergyController::ConnectedState);
emit q->connected();
});
thread->start();
}
void QLowEnergyControllerPrivateWinRT::disconnectFromDevice()
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
Q_Q(QLowEnergyController);
setState(QLowEnergyController::ClosingState);
emit abortConnection();
unregisterFromValueChanges();
unregisterFromStatusChanges();
unregisterFromMtuChanges();
clearAllServices();
mGattSession = nullptr;
mDevice = nullptr;
setState(QLowEnergyController::UnconnectedState);
emit q->disconnected();
}
bool QLowEnergyControllerPrivateWinRT::getNativeService(const QBluetoothUuid &serviceUuid,
NativeServiceCallback callback)
{
if (m_openedServices.contains(serviceUuid)) {
callback(m_openedServices.value(serviceUuid, nullptr));
return true;
}
auto servicesResultOperation = SAFE(mDevice.GetGattServicesForUuidAsync(GUID(serviceUuid)));
if (!servicesResultOperation)
RETURN_FALSE("Could not start async services request");
QPointer<QLowEnergyControllerPrivateWinRT> thisPtr(this);
bool ok = TRY(servicesResultOperation.Completed(
[thisPtr, callback, &serviceUuid](
IAsyncOperation<GattDeviceServicesResult> const &op, winrt::AsyncStatus const status)
{
if (!thisPtr) {
qCWarning(QT_BT_WINDOWS) << "LE controller was removed while getting native service";
return;
}
if (status != winrt::AsyncStatus::Completed) {
qCDebug(QT_BT_WINDOWS) << "Failed to get result of async service request";
return;
}
auto result = SAFE(op.GetResults());
if (!result)
RETURN_MSG("Failed to get result of async service request");
auto services = SAFE(result.Services());
if (!services)
RETURN_MSG("Failed to extract services from the result");
uint servicesCount = 0;
if (!TRY(servicesCount = services.Size()))
RETURN_MSG("Failed to extract services count");
if (servicesCount > 0) {
if (servicesCount > 1) {
qWarning() << "getNativeService: more than one service detected for UUID"
<< serviceUuid << "The first service will be used.";
}
auto service = SAFE(services.GetAt(0));
if (!service) {
qCDebug(QT_BT_WINDOWS) << "Could not obtain native service for Uuid"
<< serviceUuid;
} else {
thisPtr->m_openedServices.insert(serviceUuid, service);
callback(service); // Use the service in a custom callback
}
} else {
qCWarning(QT_BT_WINDOWS) << "No services found for Uuid" << serviceUuid;
}
}));
return ok;
}
bool QLowEnergyControllerPrivateWinRT::getNativeCharacteristic(
const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid,
NativeCharacteristicCallback callback)
{
QPointer<QLowEnergyControllerPrivateWinRT> thisPtr(this);
auto serviceCallback = [thisPtr, callback, charUuid](GattDeviceService service) {
auto characteristicRequestOp = SAFE(service.GetCharacteristicsForUuidAsync(GUID(charUuid)));
if (!characteristicRequestOp)
RETURN_MSG("Could not start async characteristics request");
TRY(characteristicRequestOp.Completed(
[thisPtr, callback](
IAsyncOperation<GattCharacteristicsResult> const &op, winrt::AsyncStatus const status)
{
if (!thisPtr)
return;
if (status != winrt::AsyncStatus::Completed) {
qCDebug(QT_BT_WINDOWS) << "Failed to get result of async characteristic "
"operation";
return;
}
auto result = SAFE(op.GetResults());
if (!result)
RETURN_MSG("Failed to get result of async characteristic operation");
GattCommunicationStatus commStatus;
if (!TRY(commStatus = result.Status()) || commStatus != GattCommunicationStatus::Success) {
qErrnoWarning("Native characteristic operation failed.");
return;
}
auto characteristics = SAFE(result.Characteristics());
if (!characteristics)
RETURN_MSG("Could not obtain characteristic list.");
uint size;
if (!TRY(size = characteristics.Size()))
RETURN_MSG("Could not obtain characteristic list's size.");
if (size != 1)
qErrnoWarning("More than 1 characteristic found.");
auto characteristic = SAFE(characteristics.GetAt(0));
if (!characteristic)
RETURN_MSG("Could not obtain first characteristic for service");
callback(characteristic); // use the characteristic in a custom callback
}));
};
if (!getNativeService(serviceUuid, serviceCallback)) {
qCDebug(QT_BT_WINDOWS) << "Failed to get native service for" << serviceUuid;
return false;
}
return true;
}
void QLowEnergyControllerPrivateWinRT::registerForValueChanges(const QBluetoothUuid &serviceUuid,
const QBluetoothUuid &charUuid)
{
qCDebug(QT_BT_WINDOWS) << "Registering characteristic" << charUuid << "in service"
<< serviceUuid << "for value changes";
for (const ValueChangedEntry &entry : std::as_const(mValueChangedTokens)) {
GUID guuid{ 0 };
if (!TRY(guuid = entry.characteristic.Uuid()))
WARN_AND_CONTINUE("Could not obtain characteristic's Uuid");
if (QBluetoothUuid(guuid) == charUuid)
return;
}
auto callback = [this, charUuid, serviceUuid](GattCharacteristic characteristic) {
winrt::event_token token;
if (!TRY(token = characteristic.ValueChanged({ this, &QLowEnergyControllerPrivateWinRT::onValueChange })))
RETURN_MSG("Could not register characteristic for value changes");
mValueChangedTokens.append(ValueChangedEntry(characteristic, token));
qCDebug(QT_BT_WINDOWS) << "Characteristic" << charUuid << "in service"
<< serviceUuid << "registered for value changes";
};
if (!getNativeCharacteristic(serviceUuid, charUuid, callback)) {
qCDebug(QT_BT_WINDOWS).nospace() << "Could not obtain native characteristic "
<< charUuid << " from service " << serviceUuid
<< ". Qt will not be able to signal"
<< " changes for this characteristic.";
}
}
void QLowEnergyControllerPrivateWinRT::unregisterFromValueChanges()
{
qCDebug(QT_BT_WINDOWS) << "Unregistering " << mValueChangedTokens.size() << " value change tokens";
for (const ValueChangedEntry &entry : std::as_const(mValueChangedTokens)) {
if (!entry.characteristic) {
qCWarning(QT_BT_WINDOWS) << "Unregistering from value changes for characteristic failed."
<< "Characteristic has been deleted";
continue;
}
if (!TRY(entry.characteristic.ValueChanged(entry.token)))
qCWarning(QT_BT_WINDOWS) << "Unregistering from value changes for characteristic failed.";
}
mValueChangedTokens.clear();
}
void QLowEnergyControllerPrivateWinRT::onValueChange(GattCharacteristic characteristic, GattValueChangedEventArgs const &args)
{
quint16 handle = 0;
if (!TRY(handle = characteristic.AttributeHandle()))
RETURN_MSG("Could not obtain characteristic's handle");
auto buffer = SAFE(args.CharacteristicValue());
if (!buffer)
RETURN_MSG("Could not obtain characteristic's value");
emit characteristicChanged(handle, byteArrayFromBuffer(buffer));
}
bool QLowEnergyControllerPrivateWinRT::registerForMtuChanges()
{
if (!mDevice || !mGattSession)
return false;
qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
if (!TRY(mMtuChangedToken = mGattSession.MaxPduSizeChanged({ this, &QLowEnergyControllerPrivateWinRT::onMtuChange })))
RETURN_FALSE("Could not add MTU callback");
return true;
}
void QLowEnergyControllerPrivateWinRT::unregisterFromMtuChanges()
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
if (mDevice && mGattSession && mMtuChangedToken) {
TRY(mGattSession.MaxPduSizeChanged(mMtuChangedToken));
mMtuChangedToken = { 0 };
}
}
void QLowEnergyControllerPrivateWinRT::onMtuChange(GattSession session, winrt::IInspectable args)
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
if (session != mGattSession) {
qCWarning(QT_BT_WINDOWS) << "Got MTU changed event for wrong or outdated GattSession.";
} else {
Q_Q(QLowEnergyController);
emit q->mtuChanged(mtu());
}
}
bool QLowEnergyControllerPrivateWinRT::registerForStatusChanges()
{
if (!mDevice)
return false;
qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
if (!TRY(mStatusChangedToken = mDevice.ConnectionStatusChanged({ this, &QLowEnergyControllerPrivateWinRT::onStatusChange })))
RETURN_FALSE("Could not add status callback");
return true;
}
void QLowEnergyControllerPrivateWinRT::unregisterFromStatusChanges()
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__;
if (mDevice && mStatusChangedToken) {
TRY(mDevice.ConnectionStatusChanged(mStatusChangedToken));
mStatusChangedToken = { 0 };
}
}
void QLowEnergyControllerPrivateWinRT::onStatusChange(BluetoothLEDevice dev, winrt::IInspectable args)
{
Q_Q(QLowEnergyController);
BluetoothConnectionStatus status;
if (!TRY(status = dev.ConnectionStatus()))
RETURN_MSG("Could not obtain connection status");
if (state == QLowEnergyController::ConnectingState
&& status == BluetoothConnectionStatus::Connected) {
setState(QLowEnergyController::ConnectedState);
emit q->connected();
} else if (state != QLowEnergyController::UnconnectedState
&& status == BluetoothConnectionStatus::Disconnected) {
invalidateServices();
unregisterFromValueChanges();
unregisterFromStatusChanges();
unregisterFromMtuChanges();
mGattSession = nullptr;
mDevice = nullptr;
setError(QLowEnergyController::RemoteHostClosedError);
setState(QLowEnergyController::UnconnectedState);
emit q->disconnected();
}
}
void QLowEnergyControllerPrivateWinRT::obtainIncludedServices(QSharedPointer<QLowEnergyServicePrivate> servicePointer, GattDeviceService service)
{
Q_Q(QLowEnergyController);
auto result = SAFE(await(service.GetIncludedServicesAsync()));
if (!result)
RETURN_MSG("Could not obtain included services");
// The device can be disconnected by the time we return from await()
if (state != QLowEnergyController::DiscoveringState)
return;
GattCommunicationStatus status;
if (!TRY(status = result.Status())) {
qErrnoWarning("Could not obtain list of included services");
return;
}
auto includedServices = SAFE(result.Services());
if (!includedServices)
RETURN_MSG("Could not obtain service list");
uint count;
if (!TRY(count = includedServices.Size()))
RETURN_MSG("Could not obtain service list's size");
for (uint i = 0; i < count; ++i) {
auto includedService = SAFE(includedServices.GetAt(i));
if (!includedService)
WARN_AND_CONTINUE("Could not obtain service from list");
GUID guuid;
if (!TRY(guuid = includedService.Uuid()))
WARN_AND_CONTINUE("Could not obtain included service's Uuid");
const QBluetoothUuid includedUuid(guuid);
QSharedPointer<QLowEnergyServicePrivate> includedPointer;
qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__
<< "Changing service pointer from thread"
<< QThread::currentThread();
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 QLowEnergyControllerPrivateWinRT::onServiceDiscoveryFinished(IAsyncOperation<GattDeviceServicesResult> const &op, winrt::AsyncStatus status)
{
// Check if the device is in the proper state, because it can already be
// disconnected when the callback arrives.
// Also the callback can theoretically come when the connection is
// reestablisheed again (for example, if the user quickly clicks
// "Disconnect" and then "Connect" again in some UI). But we can probably
// omit such details, as we are connecting to the same device anyway.
if (state != QLowEnergyController::DiscoveringState)
return;
Q_Q(QLowEnergyController);
if (status != winrt::AsyncStatus::Completed) {
qCDebug(QT_BT_WINDOWS) << "Could not obtain services";
return;
}
auto result = SAFE(op.GetResults());
if (!result)
return handleConnectionError("Could not obtain service discovery result");
GattCommunicationStatus commStatus;
if (!TRY(commStatus = result.Status()))
return handleConnectionError("Could not obtain service discovery status");
if (commStatus != GattCommunicationStatus::Success)
return;
auto deviceServices = SAFE(result.Services());
if (!deviceServices)
return handleConnectionError("Could not obtain service list");
uint serviceCount;
if (!TRY(serviceCount = deviceServices.Size()))
return handleConnectionError("Could not obtain service list size");
for (uint i = 0; i < serviceCount; ++i) {
auto deviceService = SAFE(deviceServices.GetAt(i));
if (!deviceService)
WARN_AND_CONTINUE("Could not obtain service");
GUID guuid;
if (!TRY(guuid = deviceService.Uuid()))
WARN_AND_CONTINUE("Could not obtain service's Uuid");
const QBluetoothUuid service(guuid);
m_openedServices.insert(service, deviceService);
qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__
<< "Changing service pointer from thread"
<< QThread::currentThread();
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);
// The obtainIncludedServices method calls await(), so the device can be
// disconnected by the time we return from it. TODO - rewrite in an
// async way!
if (state != QLowEnergyController::DiscoveringState) {
emit q->discoveryFinished(); // Probably not needed when the device
// is already disconnected?
return;
}
emit q->serviceDiscovered(service);
}
setState(QLowEnergyController::DiscoveredState);
emit q->discoveryFinished();
}
void QLowEnergyControllerPrivateWinRT::clearAllServices()
{
// These services will be closed in the respective
// QWinRTLowEnergyServiceHandler workers (in background threads).
for (auto &uuid : m_requestDetailsServiceUuids)
m_openedServices.remove(uuid);
m_requestDetailsServiceUuids.clear();
for (auto service : m_openedServices)
TRY(service.Close());
m_openedServices.clear();
}
void QLowEnergyControllerPrivateWinRT::closeAndRemoveService(const QBluetoothUuid &uuid)
{
auto record = m_openedServices.find(uuid);
if (record != m_openedServices.end()) {
auto service = record.value();
m_openedServices.erase(record);
if (service)
TRY(service.Close());
}
}
void QLowEnergyControllerPrivateWinRT::discoverServices()
{
qCDebug(QT_BT_WINDOWS) << "Service discovery initiated";
// clear the previous services cache, as we request the services again
clearAllServices();
auto asyncResult = SAFE(mDevice.GetGattServicesAsync());
if (!asyncResult)
return handleConnectionError("Could not obtain services");
if (!TRY(asyncResult.Completed({ this, &QLowEnergyControllerPrivateWinRT::onServiceDiscoveryFinished })))
return handleConnectionError("Could not register services discovery callback");
}
void QLowEnergyControllerPrivateWinRT::discoverServiceDetails(
const QBluetoothUuid &service, QLowEnergyService::DiscoveryMode mode)
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__ << service;
if (!serviceList.contains(service)) {
qCWarning(QT_BT_WINDOWS) << "Discovery done of unknown service:"
<< service.toString();
return;
}
// clear the cache to rediscover service details
closeAndRemoveService(service);
auto serviceCallback = [service, mode, this](GattDeviceService deviceService) {
discoverServiceDetailsHelper(service, mode, deviceService);
};
if (!getNativeService(service, serviceCallback))
qCDebug(QT_BT_WINDOWS) << "Could not obtain native service for uuid " << service;
}
void QLowEnergyControllerPrivateWinRT::discoverServiceDetailsHelper(
const QBluetoothUuid &service, QLowEnergyService::DiscoveryMode mode,
GattDeviceService deviceService)
{
auto reactOnDiscoveryError = [](QSharedPointer<QLowEnergyServicePrivate> service,
const auto &msg)
{
qCDebug(QT_BT_WINDOWS) << msg;
service->setError(QLowEnergyService::UnknownError);
service->setState(QLowEnergyService::RemoteService);
};
//update service data
QSharedPointer<QLowEnergyServicePrivate> pointer = serviceList.value(service);
if (!pointer) {
qCDebug(QT_BT_WINDOWS) << "Device was disconnected while doing service discovery";
return;
}
qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread"
<< QThread::currentThread();
pointer->setState(QLowEnergyService::RemoteServiceDiscovering);
auto result = SAFE(await_forever(deviceService.GetIncludedServicesAsync()));
if (!result)
return reactOnDiscoveryError(pointer, "Could not obtain included service list");
if (SAFE(result.Status() != GattCommunicationStatus::Success))
return reactOnDiscoveryError(pointer, "Obtaining list of included services failed");
auto deviceServices = SAFE(result.Services());
if (!deviceServices)
return reactOnDiscoveryError(pointer, "Could not obtain service list from result");
uint serviceCount;
if (!TRY(serviceCount = deviceServices.Size()))
return reactOnDiscoveryError(pointer, "Could not obtain included service list's size");
for (uint i = 0; i < serviceCount; ++i) {
auto includedService = SAFE(deviceServices.GetAt(i));
if (!includedService)
WARN_AND_CONTINUE("Could not obtain service from list");
GUID guuid;
if (!TRY(guuid = includedService.Uuid()))
WARN_AND_CONTINUE("Could not obtain service Uuid");
const QBluetoothUuid service(guuid);
if (service.isNull()) {
qCDebug(QT_BT_WINDOWS) << "Could not find service";
continue;
}
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, deviceService, mode);
m_requestDetailsServiceUuids.insert(service);
QThread *thread = new QThread;
worker->moveToThread(thread);
connect(thread, &QThread::started, worker, &QWinRTLowEnergyServiceHandler::obtainCharList);
connect(thread, &QThread::finished, worker, &QObject::deleteLater);
connect(worker, &QObject::destroyed, thread, &QObject::deleteLater);
connect(this, &QLowEnergyControllerPrivateWinRT::abortConnection,
worker, &QWinRTLowEnergyServiceHandler::setAbortRequested);
connect(worker, &QWinRTLowEnergyServiceHandler::errorOccured,
this, &QLowEnergyControllerPrivateWinRT::handleServiceHandlerError);
connect(worker, &QWinRTLowEnergyServiceHandler::charListObtained, this,
[this](const QBluetoothUuid &service, QHash<QLowEnergyHandle,
QLowEnergyServicePrivate::CharData> charList, QList<QBluetoothUuid> indicateChars,
QLowEnergyHandle startHandle, QLowEnergyHandle endHandle) {
if (!serviceList.contains(service)) {
qCWarning(QT_BT_WINDOWS)
<< "Discovery complete for unknown service:" << service.toString();
return;
}
m_requestDetailsServiceUuids.remove(service);
QSharedPointer<QLowEnergyServicePrivate> pointer = serviceList.value(service);
pointer->startHandle = startHandle;
pointer->endHandle = endHandle;
pointer->characteristicList = charList;
for (const QBluetoothUuid &indicateChar : std::as_const(indicateChars))
registerForValueChanges(service, indicateChar);
pointer->setState(QLowEnergyService::RemoteServiceDiscovered);
});
thread->start();
}
void QLowEnergyControllerPrivateWinRT::startAdvertising(
const QLowEnergyAdvertisingParameters &,
const QLowEnergyAdvertisingData &,
const QLowEnergyAdvertisingData &)
{
setError(QLowEnergyController::AdvertisingError);
Q_UNIMPLEMENTED();
}
void QLowEnergyControllerPrivateWinRT::stopAdvertising()
{
Q_UNIMPLEMENTED();
}
void QLowEnergyControllerPrivateWinRT::requestConnectionUpdate(const QLowEnergyConnectionParameters &)
{
Q_UNIMPLEMENTED();
}
void QLowEnergyControllerPrivateWinRT::readCharacteristic(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle)
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__ << service << charHandle;
qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread"
<< QThread::currentThread();
Q_ASSERT(!service.isNull());
if (role == QLowEnergyController::PeripheralRole) {
service->setError(QLowEnergyService::CharacteristicReadError);
Q_UNIMPLEMENTED();
return;
}
if (!service->characteristicList.contains(charHandle)) {
qCDebug(QT_BT_WINDOWS) << charHandle << "could not be found in service" << service->uuid;
service->setError(QLowEnergyService::CharacteristicReadError);
return;
}
const auto charData = service->characteristicList.value(charHandle);
if (!(charData.properties & QLowEnergyCharacteristic::Read))
qCDebug(QT_BT_WINDOWS) << "Read flag is not set for characteristic" << charData.uuid;
auto characteristicCallback = [charHandle, service, this](
GattCharacteristic characteristic) {
readCharacteristicHelper(service, charHandle, characteristic);
};
if (!getNativeCharacteristic(service->uuid, charData.uuid, characteristicCallback)) {
qCDebug(QT_BT_WINDOWS) << "Could not obtain native characteristic" << charData.uuid
<< "from service" << service->uuid;
service->setError(QLowEnergyService::CharacteristicReadError);
}
}
void QLowEnergyControllerPrivateWinRT::readCharacteristicHelper(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
GattCharacteristic characteristic)
{
auto readOp = SAFE(characteristic.ReadValueAsync(BluetoothCacheMode::Uncached));
if (!readOp)
RETURN_SERVICE_ERROR("Could not read characteristic", service, QLowEnergyService::CharacteristicReadError);
auto readCompletedLambda = [charHandle, service]
(IAsyncOperation<GattReadResult> const &op, winrt::AsyncStatus const status)
{
if (status == winrt::AsyncStatus::Canceled || status == winrt::AsyncStatus::Error) {
qCDebug(QT_BT_WINDOWS) << "Characteristic" << charHandle << "read operation failed.";
service->setError(QLowEnergyService::CharacteristicReadError);
return;
}
auto characteristicValue = SAFE(op.GetResults());
if (!characteristicValue)
RETURN_SERVICE_ERROR("Could not obtain result for characteristic", service, QLowEnergyService::CharacteristicReadError);
const QByteArray value = byteArrayFromGattResult(characteristicValue);
auto charData = service->characteristicList.value(charHandle);
charData.value = value;
service->characteristicList.insert(charHandle, charData);
emit service->characteristicRead(QLowEnergyCharacteristic(service, charHandle), value);
return;
};
if (!TRY(readOp.Completed(readCompletedLambda)))
RETURN_SERVICE_ERROR("Could not register characteristic read callback", service, QLowEnergyService::CharacteristicReadError);
}
void QLowEnergyControllerPrivateWinRT::readDescriptor(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
const QLowEnergyHandle descHandle)
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__ << service << charHandle << descHandle;
qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread"
<< QThread::currentThread();
Q_ASSERT(!service.isNull());
if (role == QLowEnergyController::PeripheralRole) {
service->setError(QLowEnergyService::DescriptorReadError);
Q_UNIMPLEMENTED();
return;
}
if (!service->characteristicList.contains(charHandle)) {
qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "in characteristic" << charHandle
<< "cannot be found in service" << service->uuid;
service->setError(QLowEnergyService::DescriptorReadError);
return;
}
const auto charData = service->characteristicList.value(charHandle);
auto characteristicCallback = [charHandle, descHandle, service, this](
GattCharacteristic characteristic) {
readDescriptorHelper(service, charHandle, descHandle, characteristic);
};
if (!getNativeCharacteristic(service->uuid, charData.uuid, characteristicCallback)) {
qCDebug(QT_BT_WINDOWS) << "Could not obtain native characteristic" << charData.uuid
<< "from service" << service->uuid;
service->setError(QLowEnergyService::DescriptorReadError);
}
}
void QLowEnergyControllerPrivateWinRT::readDescriptorHelper(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
const QLowEnergyHandle descHandle,
GattCharacteristic characteristic)
{
// Get native descriptor
const auto charData = service->characteristicList.value(charHandle);
if (!charData.descriptorList.contains(descHandle)) {
qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "cannot be found in characteristic"
<< charHandle;
}
const auto descData = charData.descriptorList.value(descHandle);
const QBluetoothUuid descUuid = descData.uuid;
if (descUuid ==
QBluetoothUuid(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration)) {
auto readOp = SAFE(characteristic.ReadClientCharacteristicConfigurationDescriptorAsync());
if (!readOp)
RETURN_SERVICE_ERROR("Could not read client characteristic configuration", service, QLowEnergyService::DescriptorReadError);
auto readCompletedLambda = [charHandle, descHandle, service]
(IAsyncOperation<ClientCharConfigDescriptorResult> const &op, winrt::AsyncStatus const status)
{
if (status == winrt::AsyncStatus::Canceled || status == winrt::AsyncStatus::Error) {
qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "read operation failed";
service->setError(QLowEnergyService::DescriptorReadError);
return;
}
auto iValue = SAFE(op.GetResults());
if (!iValue)
RETURN_SERVICE_ERROR("Could not obtain result for descriptor", service, QLowEnergyService::DescriptorReadError);
GattClientCharacteristicConfigurationDescriptorValue value;
if (!TRY(value = iValue.ClientCharacteristicConfigurationDescriptor()))
RETURN_SERVICE_ERROR("Could not obtain value for descriptor", service, QLowEnergyService::DescriptorReadError);
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_WINDOWS) << "Descriptor" << descHandle
<< "read operation failed. Obtained unexpected value.";
service->setError(QLowEnergyService::DescriptorReadError);
return;
}
QLowEnergyServicePrivate::DescData descData;
descData.uuid = QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration;
descData.value = QByteArray(2, Qt::Uninitialized);
qToLittleEndian(result, descData.value.data());
service->characteristicList[charHandle].descriptorList[descHandle] = descData;
emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle),
descData.value);
return;
};
if (!TRY(readOp.Completed(readCompletedLambda)))
RETURN_SERVICE_ERROR("Could not register descriptor read callback", service, QLowEnergyService::DescriptorReadError);
return;
}
auto result = SAFE(await(characteristic.GetDescriptorsForUuidAsync(GUID(descData.uuid))));
if (!result)
RETURN_SERVICE_ERROR("Could not obtain descriptor for uuid", service, QLowEnergyService::DescriptorReadError);
GattCommunicationStatus commStatus;
if (!TRY(commStatus = result.Status()) || commStatus != GattCommunicationStatus::Success) {
qErrnoWarning("Could not obtain list of descriptors");
service->setError(QLowEnergyService::DescriptorReadError);
return;
}
auto descriptors = SAFE(result.Descriptors());
if (!descriptors)
RETURN_SERVICE_ERROR("Could not obtain descriptor list", service, QLowEnergyService::DescriptorReadError);
uint size;
if (!TRY(size = descriptors.Size()))
RETURN_SERVICE_ERROR("Could not obtain descriptor list size", service, QLowEnergyService::DescriptorReadError);
if (size == 0) {
qCWarning(QT_BT_WINDOWS) << "No descriptor with uuid" << descData.uuid << "was found.";
service->setError(QLowEnergyService::DescriptorReadError);
return;
} else if (size > 1) {
qCWarning(QT_BT_WINDOWS) << "There is more than 1 descriptor with uuid" << descData.uuid;
}
auto descriptor = SAFE(descriptors.GetAt(0));
if (!descriptor)
RETURN_SERVICE_ERROR("Could not obtain descriptor from list", service, QLowEnergyService::DescriptorReadError);
auto readOp = SAFE(descriptor.ReadValueAsync(BluetoothCacheMode::Uncached));
if (!readOp)
RETURN_SERVICE_ERROR("Could not read descriptor value", service, QLowEnergyService::DescriptorReadError);
auto readCompletedLambda = [charHandle, descHandle, descUuid, service]
(IAsyncOperation<GattReadResult> const &op, winrt::AsyncStatus const status)
{
if (status == winrt::AsyncStatus::Canceled || status == winrt::AsyncStatus::Error) {
qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "read operation failed";
service->setError(QLowEnergyService::DescriptorReadError);
return;
}
auto descriptorValue = SAFE(op.GetResults());
if (!descriptorValue) {
qCDebug(QT_BT_WINDOWS) << "Could not obtain result for descriptor" << descHandle;
service->setError(QLowEnergyService::DescriptorReadError);
return;
}
QLowEnergyServicePrivate::DescData descData;
descData.uuid = descUuid;
if (descData.uuid == QBluetoothUuid::DescriptorType::CharacteristicUserDescription)
descData.value = byteArrayFromGattResult(descriptorValue, true);
else
descData.value = byteArrayFromGattResult(descriptorValue);
service->characteristicList[charHandle].descriptorList[descHandle] = descData;
emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle),
descData.value);
};
if (!TRY(readOp.Completed(readCompletedLambda)))
RETURN_SERVICE_ERROR("Could not register descriptor read callback", service, QLowEnergyService::DescriptorReadError);
}
void QLowEnergyControllerPrivateWinRT::writeCharacteristic(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
const QByteArray &newValue,
QLowEnergyService::WriteMode mode)
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__ << service << charHandle << newValue << mode;
qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread"
<< QThread::currentThread();
Q_ASSERT(!service.isNull());
if (role == QLowEnergyController::PeripheralRole) {
service->setError(QLowEnergyService::CharacteristicWriteError);
Q_UNIMPLEMENTED();
return;
}
if (!service->characteristicList.contains(charHandle)) {
qCDebug(QT_BT_WINDOWS) << "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_WINDOWS) << "Write flag is not set for characteristic" << charHandle;
auto characteristicCallback = [charHandle, service, newValue, writeWithResponse, this](
GattCharacteristic characteristic) {
writeCharacteristicHelper(service, charHandle, newValue, writeWithResponse,
characteristic);
};
if (!getNativeCharacteristic(service->uuid, charData.uuid, characteristicCallback)) {
qCDebug(QT_BT_WINDOWS) << "Could not obtain native characteristic" << charData.uuid
<< "from service" << service->uuid;
service->setError(QLowEnergyService::CharacteristicWriteError);
}
}
void QLowEnergyControllerPrivateWinRT::writeCharacteristicHelper(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle, const QByteArray &newValue,
bool writeWithResponse, GattCharacteristic characteristic)
{
const quint32 length = quint32(newValue.length());
Buffer buffer = nullptr;
if (!TRY(buffer = Buffer(length)))
RETURN_SERVICE_ERROR("Could not create buffer", service, QLowEnergyService::CharacteristicWriteError);
byte *bytes;
if (!TRY(bytes = buffer.data()))
RETURN_SERVICE_ERROR("Could not set buffer", service, QLowEnergyService::CharacteristicWriteError);
memcpy(bytes, newValue, length);
if (!TRY(buffer.Length(length)))
RETURN_SERVICE_ERROR("Could not set buffer length", service, QLowEnergyService::CharacteristicWriteError);
GattWriteOption option = writeWithResponse ? GattWriteOption::WriteWithResponse
: GattWriteOption::WriteWithoutResponse;
auto writeOp = SAFE(characteristic.WriteValueAsync(buffer, option));
if (!writeOp)
RETURN_SERVICE_ERROR("Could not write characteristic", service, QLowEnergyService::CharacteristicWriteError);
const auto charData = service->characteristicList.value(charHandle);
QPointer<QLowEnergyControllerPrivateWinRT> thisPtr(this);
auto writeCompletedLambda =
[charData, charHandle, newValue, service, writeWithResponse, thisPtr]
(IAsyncOperation<GattCommunicationStatus> const &op, winrt::AsyncStatus const status)
{
if (status == winrt::AsyncStatus::Canceled || status == winrt::AsyncStatus::Error) {
qCDebug(QT_BT_WINDOWS) << "Characteristic" << charHandle
<< "write operation failed (async status)";
service->setError(QLowEnergyService::CharacteristicWriteError);
return;
}
GattCommunicationStatus result;
auto hr = HR(result = op.GetResults());
if (hr == E_BLUETOOTH_ATT_INVALID_ATTRIBUTE_VALUE_LENGTH) {
qCDebug(QT_BT_WINDOWS) << "Characteristic" << charHandle
<< "write operation was tried with invalid value length";
service->setError(QLowEnergyService::CharacteristicWriteError);
return;
} else if (FAILED(hr)) {
RETURN_SERVICE_ERROR("Could not obtain characteristic write result", service, QLowEnergyService::CharacteristicWriteError);
}
if (result != GattCommunicationStatus::Success) {
qCDebug(QT_BT_WINDOWS) << "Characteristic" << charHandle
<< "write operation failed (communication status)";
service->setError(QLowEnergyService::CharacteristicWriteError);
return;
}
// only update cache when property is readable. Otherwise it remains
// empty.
if (thisPtr && charData.properties & QLowEnergyCharacteristic::Read)
thisPtr->updateValueOfCharacteristic(charHandle, newValue, false);
if (writeWithResponse) {
emit service->characteristicWritten(QLowEnergyCharacteristic(service, charHandle),
newValue);
}
};
if (!TRY(writeOp.Completed(writeCompletedLambda)))
RETURN_SERVICE_ERROR("Could not register characteristic write callback", service, QLowEnergyService::CharacteristicWriteError);
}
void QLowEnergyControllerPrivateWinRT::writeDescriptor(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
const QLowEnergyHandle descHandle,
const QByteArray &newValue)
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__ << service << charHandle << descHandle << newValue;
qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread"
<< QThread::currentThread();
Q_ASSERT(!service.isNull());
if (role == QLowEnergyController::PeripheralRole) {
service->setError(QLowEnergyService::DescriptorWriteError);
Q_UNIMPLEMENTED();
return;
}
if (!service->characteristicList.contains(charHandle)) {
qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "in characteristic" << charHandle
<< "could not be found in service" << service->uuid;
service->setError(QLowEnergyService::DescriptorWriteError);
return;
}
const auto charData = service->characteristicList.value(charHandle);
auto characteristicCallback = [descHandle, charHandle, service, newValue, this](
GattCharacteristic characteristic) {
writeDescriptorHelper(service, charHandle, descHandle, newValue, characteristic);
};
if (!getNativeCharacteristic(service->uuid, charData.uuid, characteristicCallback)) {
qCDebug(QT_BT_WINDOWS) << "Could not obtain native characteristic" << charData.uuid
<< "from service" << service->uuid;
service->setError(QLowEnergyService::DescriptorWriteError);
}
}
void QLowEnergyControllerPrivateWinRT::writeDescriptorHelper(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
const QLowEnergyHandle descHandle,
const QByteArray &newValue,
GattCharacteristic characteristic)
{
// Get native descriptor
const auto charData = service->characteristicList.value(charHandle);
if (!charData.descriptorList.contains(descHandle)) {
qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle
<< "could not be found in Characteristic" << charHandle;
}
QLowEnergyServicePrivate::DescData descData = charData.descriptorList.value(descHandle);
if (descData.uuid ==
QBluetoothUuid(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration)) {
GattClientCharacteristicConfigurationDescriptorValue value;
quint16 intValue = qFromLittleEndian<quint16>(newValue);
if ((intValue & GattClientCharacteristicConfigurationDescriptorValue::Indicate)
&& (intValue & GattClientCharacteristicConfigurationDescriptorValue::Notify)) {
qCWarning(QT_BT_WINDOWS) << "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_WINDOWS) << "Descriptor" << descHandle
<< "write operation failed: Invalid value";
service->setError(QLowEnergyService::DescriptorWriteError);
return;
}
auto writeOp = SAFE(characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(value));
if (!writeOp)
RETURN_SERVICE_ERROR("Could not write client characteristic configuration", service, QLowEnergyService::DescriptorWriteError);
QPointer<QLowEnergyControllerPrivateWinRT> thisPtr(this);
auto writeCompletedLambda = [charHandle, descHandle, newValue, service, thisPtr]
(IAsyncOperation<GattCommunicationStatus> const &op, winrt::AsyncStatus const status)
{
if (status == winrt::AsyncStatus::Canceled || status == winrt::AsyncStatus::Error) {
qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "write operation failed";
service->setError(QLowEnergyService::DescriptorWriteError);
return;
}
GattCommunicationStatus result;
if (!TRY(result = op.GetResults()))
RETURN_SERVICE_ERROR("Could not obtain result for descriptor", service, QLowEnergyService::DescriptorWriteError);
if (result != GattCommunicationStatus::Success) {
qCWarning(QT_BT_WINDOWS) << "Descriptor" << descHandle << "write operation failed";
service->setError(QLowEnergyService::DescriptorWriteError);
return;
}
if (thisPtr)
thisPtr->updateValueOfDescriptor(charHandle, descHandle, newValue, false);
emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle),
newValue);
};
if (!TRY(writeOp.Completed(writeCompletedLambda)))
RETURN_SERVICE_ERROR("Could not register descriptor write callback", service, QLowEnergyService::DescriptorWriteError);
return;
}
auto result = SAFE(await(characteristic.GetDescriptorsForUuidAsync(GUID(descData.uuid))));
if (!result)
RETURN_SERVICE_ERROR("Could not obtain descriptor from Uuid", service, QLowEnergyService::DescriptorWriteError);
GattCommunicationStatus commStatus;
if (!TRY(commStatus = result.Status()) || commStatus != GattCommunicationStatus::Success) {
qCWarning(QT_BT_WINDOWS) << "Descriptor operation failed";
service->setError(QLowEnergyService::DescriptorWriteError);
return;
}
auto descriptors = SAFE(result.Descriptors());
if (!descriptors)
RETURN_SERVICE_ERROR("Could not obtain list of descriptors", service, QLowEnergyService::DescriptorWriteError);
uint size;
if (!TRY(size = descriptors.Size()))
RETURN_SERVICE_ERROR("Could not obtain list of descriptors' size", service, QLowEnergyService::DescriptorWriteError);
if (size == 0) {
qCWarning(QT_BT_WINDOWS) << "No descriptor with uuid" << descData.uuid << "was found.";
return;
} else if (size > 1) {
qCWarning(QT_BT_WINDOWS) << "There is more than 1 descriptor with uuid" << descData.uuid;
}
auto descriptor = SAFE(descriptors.GetAt(0));
if (!descriptor)
RETURN_SERVICE_ERROR("Could not obtain descriptor", service, QLowEnergyService::DescriptorWriteError);
const quint32 length = quint32(newValue.length());
Buffer buffer = nullptr;
if (!TRY(buffer = Buffer(length)))
RETURN_SERVICE_ERROR("Could not create buffer", service, QLowEnergyService::CharacteristicWriteError);
byte *bytes;
if (!TRY(bytes = buffer.data()))
RETURN_SERVICE_ERROR("Could not set buffer", service, QLowEnergyService::CharacteristicWriteError);
memcpy(bytes, newValue, length);
if (!TRY(buffer.Length(length)))
RETURN_SERVICE_ERROR("Could not set buffer length", service, QLowEnergyService::CharacteristicWriteError);
auto writeOp = SAFE(descriptor.WriteValueAsync(buffer));
if (!writeOp)
RETURN_SERVICE_ERROR("Could not write descriptor value", service, QLowEnergyService::CharacteristicWriteError);
QPointer<QLowEnergyControllerPrivateWinRT> thisPtr(this);
auto writeCompletedLambda = [charHandle, descHandle, newValue, service, thisPtr]
(IAsyncOperation<GattCommunicationStatus> const &op, winrt::AsyncStatus const status)
{
if (status == winrt::AsyncStatus::Canceled || status == winrt::AsyncStatus::Error) {
qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "write operation failed";
service->setError(QLowEnergyService::DescriptorWriteError);
return;
}
GattCommunicationStatus result;
if (!TRY(result = op.GetResults()))
RETURN_SERVICE_ERROR("Could not obtain result for descriptor", service, QLowEnergyService::DescriptorWriteError);
if (result != GattCommunicationStatus::Success) {
qCDebug(QT_BT_WINDOWS) << "Descriptor" << descHandle << "write operation failed";
service->setError(QLowEnergyService::DescriptorWriteError);
return;
}
if (thisPtr)
thisPtr->updateValueOfDescriptor(charHandle, descHandle, newValue, false);
emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle),
newValue);
};
if (!TRY(writeOp.Completed(writeCompletedLambda)))
RETURN_SERVICE_ERROR("Could not register descriptor write callback", service, QLowEnergyService::DescriptorWriteError);
}
void QLowEnergyControllerPrivateWinRT::addToGenericAttributeList(const QLowEnergyServiceData &,
QLowEnergyHandle)
{
Q_UNIMPLEMENTED();
}
int QLowEnergyControllerPrivateWinRT::mtu() const
{
uint16_t mtu = 23;
if (!mGattSession) {
qCDebug(QT_BT_WINDOWS) << "mtu queried before GattSession available. Using default mtu.";
return mtu;
}
if (!TRY(mtu = mGattSession.MaxPduSize()))
RETURN_FALSE("could not obtain MTU size");
qCDebug(QT_BT_WINDOWS) << "mtu determined to be" << mtu;
return mtu;
}
void QLowEnergyControllerPrivateWinRT::handleCharacteristicChanged(
quint16 charHandle, const QByteArray &data)
{
qCDebug(QT_BT_WINDOWS) << __FUNCTION__ << charHandle << data;
qCDebug(QT_BT_WINDOWS_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread"
<< QThread::currentThread();
QSharedPointer<QLowEnergyServicePrivate> service =
serviceForHandle(charHandle);
if (service.isNull())
return;
qCDebug(QT_BT_WINDOWS) << "Characteristic change notification" << service->uuid
<< charHandle << data.toHex();
QLowEnergyCharacteristic characteristic = characteristicForHandle(charHandle);
if (!characteristic.isValid()) {
qCWarning(QT_BT_WINDOWS) << "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);
}
void QLowEnergyControllerPrivateWinRT::handleServiceHandlerError(const QString &error)
{
if (state != QLowEnergyController::DiscoveringState)
return;
qCWarning(QT_BT_WINDOWS) << "Error while discovering services:" << error;
setState(QLowEnergyController::UnconnectedState);
setError(QLowEnergyController::ConnectionError);
}
void QLowEnergyControllerPrivateWinRT::handleConnectionError(const char *logMessage)
{
qCWarning(QT_BT_WINDOWS) << logMessage;
setError(QLowEnergyController::ConnectionError);
setState(QLowEnergyController::UnconnectedState);
unregisterFromStatusChanges();
unregisterFromMtuChanges();
}
QT_END_NAMESPACE
#include "qlowenergycontroller_winrt.moc"