| /**************************************************************************** |
| ** |
| ** 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_winrt_new_p.h" |
| #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> |
| |
| #ifdef CLASSIC_APP_BUILD |
| #define Q_OS_WINRT |
| #endif |
| #include <QtCore/qfunctions_winrt.h> |
| #include <QtCore/QtEndian> |
| #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.devices.bluetooth.genericattributeprofile.h> |
| #include <windows.foundation.collections.h> |
| #include <windows.foundation.metadata.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::Foundation::Metadata; |
| 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; |
| |
| #define EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, ret) \ |
| if (FAILED(hr)) { \ |
| emitErrorAndQuitThread(hr); \ |
| ret; \ |
| } |
| |
| #define WARN_AND_CONTINUE_IF_FAILED(hr, msg) \ |
| if (FAILED(hr)) { \ |
| qCWarning(QT_BT_WINRT) << msg; \ |
| continue; \ |
| } |
| |
| #define CHECK_FOR_DEVICE_CONNECTION_ERROR_IMPL(this, hr, msg, ret) \ |
| if (FAILED(hr)) { \ |
| qCWarning(QT_BT_WINRT) << msg; \ |
| this->unregisterFromStatusChanges(); \ |
| this->setError(QLowEnergyController::ConnectionError); \ |
| this->setState(QLowEnergyController::UnconnectedState); \ |
| ret; \ |
| } |
| |
| #define CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, msg, ret) \ |
| CHECK_FOR_DEVICE_CONNECTION_ERROR_IMPL(this, hr, msg, ret) |
| |
| #define CHECK_HR_AND_SET_SERVICE_ERROR(hr, msg, service, error, ret) \ |
| if (FAILED(hr)) { \ |
| qCDebug(QT_BT_WINRT) << msg; \ |
| service->setError(error); \ |
| ret; \ |
| } |
| |
| Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) |
| Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT_SERVICE_THREAD) |
| |
| QLowEnergyControllerPrivate *createWinRTLowEnergyController() |
| { |
| if (supportsNewLEApi()) { |
| qCDebug(QT_BT_WINRT) << "Using new low energy controller"; |
| return new QLowEnergyControllerPrivateWinRTNew(); |
| } |
| |
| qCDebug(QT_BT_WINRT) << "Using pre 15063 low energy controller"; |
| return new QLowEnergyControllerPrivateWinRT(); |
| } |
| |
| static QByteArray byteArrayFromGattResult(const ComPtr<IGattReadResult> &gattResult, |
| bool isWCharString = false) |
| { |
| ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; |
| HRESULT hr; |
| hr = gattResult->get_Value(&buffer); |
| if (FAILED(hr) || !buffer) { |
| qCWarning(QT_BT_WINRT) << "Could not obtain buffer from GattReadResult"; |
| return QByteArray(); |
| } |
| return byteArrayFromBuffer(buffer, isWCharString); |
| } |
| |
| class QWinRTLowEnergyServiceHandlerNew : public QObject |
| { |
| Q_OBJECT |
| public: |
| QWinRTLowEnergyServiceHandlerNew(const QBluetoothUuid &service, |
| const ComPtr<IGattDeviceService3> &deviceService) |
| : mService(service) |
| , mDeviceService(deviceService) |
| { |
| qCDebug(QT_BT_WINRT) << __FUNCTION__; |
| } |
| |
| ~QWinRTLowEnergyServiceHandlerNew() |
| { |
| } |
| |
| public slots: |
| void obtainCharList() |
| { |
| mIndicateChars.clear(); |
| qCDebug(QT_BT_WINRT) << __FUNCTION__; |
| ComPtr<IAsyncOperation<GattCharacteristicsResult *>> characteristicsOp; |
| ComPtr<IGattCharacteristicsResult> characteristicsResult; |
| HRESULT hr = mDeviceService->GetCharacteristicsAsync(&characteristicsOp); |
| EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); |
| hr = QWinRTFunctions::await(characteristicsOp, characteristicsResult.GetAddressOf(), |
| QWinRTFunctions::ProcessMainThreadEvents, 5000); |
| EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); |
| GattCommunicationStatus status; |
| hr = characteristicsResult->get_Status(&status); |
| EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); |
| if (status != GattCommunicationStatus_Success) { |
| emitErrorAndQuitThread(QLatin1String("Could not obtain char list")); |
| return; |
| } |
| ComPtr<IVectorView<GattCharacteristic *>> characteristics; |
| hr = characteristicsResult->get_Characteristics(&characteristics); |
| EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); |
| |
| uint characteristicsCount; |
| hr = characteristics->get_Size(&characteristicsCount); |
| EMIT_WORKER_ERROR_AND_QUIT_IF_FAILED(hr, return); |
| |
| mCharacteristicsCountToBeDiscovered = characteristicsCount; |
| for (uint i = 0; i < characteristicsCount; ++i) { |
| ComPtr<IGattCharacteristic> characteristic; |
| hr = characteristics->GetAt(i, &characteristic); |
| if (FAILED(hr)) { |
| qCWarning(QT_BT_WINRT) << "Could not obtain characteristic at" << i; |
| --mCharacteristicsCountToBeDiscovered; |
| continue; |
| } |
| |
| ComPtr<IGattCharacteristic3> characteristic3; |
| hr = characteristic.As(&characteristic3); |
| if (FAILED(hr)) { |
| qCWarning(QT_BT_WINRT) << "Could not cast characteristic"; |
| --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. |
| ComPtr<IAsyncOperation<GattDescriptorsResult*>> descAsyncResult; |
| hr = characteristic3->GetDescriptorsAsync(&descAsyncResult); |
| if (FAILED(hr)) { |
| qCWarning(QT_BT_WINRT) << "Could not obtain list of descriptors"; |
| --mCharacteristicsCountToBeDiscovered; |
| continue; |
| } |
| hr = descAsyncResult->put_Completed( |
| Callback<IAsyncOperationCompletedHandler<GattDescriptorsResult*>>( |
| [this, characteristic] |
| (IAsyncOperation<GattDescriptorsResult *> *op, |
| AsyncStatus status) { |
| if (status != AsyncStatus::Completed) { |
| qCWarning(QT_BT_WINRT) << "Descriptor operation unsuccessful"; |
| --mCharacteristicsCountToBeDiscovered; |
| checkAllCharacteristicsDiscovered(); |
| return S_OK; |
| } |
| quint16 handle; |
| |
| HRESULT hr = characteristic->get_AttributeHandle(&handle); |
| if (FAILED(hr)) { |
| qCWarning(QT_BT_WINRT) << "Could not obtain characteristic's attribute handle"; |
| --mCharacteristicsCountToBeDiscovered; |
| checkAllCharacteristicsDiscovered(); |
| return S_OK; |
| } |
| QLowEnergyServicePrivate::CharData charData; |
| charData.valueHandle = handle + 1; |
| if (mStartHandle == 0 || mStartHandle > handle) |
| mStartHandle = handle; |
| if (mEndHandle == 0 || mEndHandle < handle) |
| mEndHandle = handle; |
| GUID guuid; |
| hr = characteristic->get_Uuid(&guuid); |
| if (FAILED(hr)) { |
| qCWarning(QT_BT_WINRT) << "Could not obtain characteristic's Uuid"; |
| --mCharacteristicsCountToBeDiscovered; |
| checkAllCharacteristicsDiscovered(); |
| return S_OK; |
| } |
| charData.uuid = QBluetoothUuid(guuid); |
| GattCharacteristicProperties properties; |
| hr = characteristic->get_CharacteristicProperties(&properties); |
| if (FAILED(hr)) { |
| qCWarning(QT_BT_WINRT) << "Could not obtain characteristic's properties"; |
| --mCharacteristicsCountToBeDiscovered; |
| checkAllCharacteristicsDiscovered(); |
| return S_OK; |
| } |
| charData.properties = QLowEnergyCharacteristic::PropertyTypes(properties & 0xff); |
| if (charData.properties & QLowEnergyCharacteristic::Read) { |
| ComPtr<IAsyncOperation<GattReadResult *>> readOp; |
| hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, |
| &readOp); |
| if (FAILED(hr)) { |
| qCWarning(QT_BT_WINRT) << "Could not read characteristic"; |
| --mCharacteristicsCountToBeDiscovered; |
| checkAllCharacteristicsDiscovered(); |
| return S_OK; |
| } |
| ComPtr<IGattReadResult> readResult; |
| hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); |
| if (FAILED(hr)) { |
| qCWarning(QT_BT_WINRT) << "Could not obtain characteristic read result"; |
| --mCharacteristicsCountToBeDiscovered; |
| checkAllCharacteristicsDiscovered(); |
| return S_OK; |
| } |
| if (!readResult) |
| qCWarning(QT_BT_WINRT) << "Characteristic read result is null"; |
| else |
| charData.value = byteArrayFromGattResult(readResult); |
| } |
| mCharacteristicList.insert(handle, charData); |
| |
| ComPtr<IVectorView<GattDescriptor *>> descriptors; |
| |
| ComPtr<IGattDescriptorsResult> result; |
| hr = op->GetResults(&result); |
| if (FAILED(hr)) { |
| qCWarning(QT_BT_WINRT) << "Could not obtain descriptor read result"; |
| --mCharacteristicsCountToBeDiscovered; |
| checkAllCharacteristicsDiscovered(); |
| return S_OK; |
| } |
| GattCommunicationStatus commStatus; |
| hr = result->get_Status(&commStatus); |
| if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { |
| qCWarning(QT_BT_WINRT) << "Descriptor operation failed"; |
| --mCharacteristicsCountToBeDiscovered; |
| checkAllCharacteristicsDiscovered(); |
| return S_OK; |
| } |
| |
| hr = result->get_Descriptors(&descriptors); |
| if (FAILED(hr)) { |
| qCWarning(QT_BT_WINRT) << "Could not obtain list of descriptors"; |
| --mCharacteristicsCountToBeDiscovered; |
| checkAllCharacteristicsDiscovered(); |
| return S_OK; |
| } |
| |
| uint descriptorCount; |
| hr = descriptors->get_Size(&descriptorCount); |
| if (FAILED(hr)) { |
| qCWarning(QT_BT_WINRT) << "Could not obtain list of descriptors' size"; |
| --mCharacteristicsCountToBeDiscovered; |
| checkAllCharacteristicsDiscovered(); |
| return S_OK; |
| } |
| for (uint j = 0; j < descriptorCount; ++j) { |
| QLowEnergyServicePrivate::DescData descData; |
| ComPtr<IGattDescriptor> descriptor; |
| hr = descriptors->GetAt(j, &descriptor); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor") |
| quint16 descHandle; |
| hr = descriptor->get_AttributeHandle(&descHandle); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor's attribute handle") |
| GUID descriptorUuid; |
| hr = descriptor->get_Uuid(&descriptorUuid); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain descriptor's Uuid") |
| descData.uuid = QBluetoothUuid(descriptorUuid); |
| charData.descriptorList.insert(descHandle, descData); |
| if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { |
| ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp; |
| hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "Could not read descriptor value") |
| ComPtr<IClientCharConfigDescriptorResult> readResult; |
| hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "Could not await descriptor read result") |
| GattClientCharacteristicConfigurationDescriptorValue value; |
| hr = readResult->get_ClientCharacteristicConfigurationDescriptor(&value); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "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; |
| |
| descData.value = QByteArray(2, Qt::Uninitialized); |
| qToLittleEndian(result, descData.value.data()); |
| mIndicateChars << charData.uuid; |
| } else { |
| ComPtr<IAsyncOperation<GattReadResult *>> readOp; |
| hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, |
| &readOp); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "Could not read descriptor value") |
| ComPtr<IGattReadResult> readResult; |
| hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "Could await descriptor read result") |
| if (descData.uuid == QBluetoothUuid::CharacteristicUserDescription) |
| descData.value = byteArrayFromGattResult(readResult, true); |
| else |
| descData.value = byteArrayFromGattResult(readResult); |
| } |
| charData.descriptorList.insert(descHandle, descData); |
| } |
| |
| mCharacteristicList.insert(handle, charData); |
| --mCharacteristicsCountToBeDiscovered; |
| checkAllCharacteristicsDiscovered(); |
| return S_OK; |
| }).Get()); |
| if (FAILED(hr)) { |
| qCWarning(QT_BT_WINRT) << "Could not register descriptor callback"; |
| --mCharacteristicsCountToBeDiscovered; |
| continue; |
| } |
| } |
| checkAllCharacteristicsDiscovered(); |
| } |
| |
| private: |
| bool checkAllCharacteristicsDiscovered(); |
| void emitErrorAndQuitThread(HRESULT hr); |
| void emitErrorAndQuitThread(const QString &error); |
| |
| public: |
| QBluetoothUuid mService; |
| ComPtr<IGattDeviceService3> mDeviceService; |
| QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> mCharacteristicList; |
| uint mCharacteristicsCountToBeDiscovered; |
| quint16 mStartHandle = 0; |
| quint16 mEndHandle = 0; |
| QVector<QBluetoothUuid> mIndicateChars; |
| |
| signals: |
| void charListObtained(const QBluetoothUuid &service, QHash<QLowEnergyHandle, |
| QLowEnergyServicePrivate::CharData> charList, |
| QVector<QBluetoothUuid> indicateChars, |
| QLowEnergyHandle startHandle, QLowEnergyHandle endHandle); |
| void errorOccured(const QString &error); |
| }; |
| |
| bool QWinRTLowEnergyServiceHandlerNew::checkAllCharacteristicsDiscovered() |
| { |
| if (mCharacteristicsCountToBeDiscovered == 0) { |
| emit charListObtained(mService, mCharacteristicList, mIndicateChars, |
| mStartHandle, mEndHandle); |
| QThread::currentThread()->quit(); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void QWinRTLowEnergyServiceHandlerNew::emitErrorAndQuitThread(HRESULT hr) |
| { |
| emitErrorAndQuitThread(qt_error_string(hr)); |
| } |
| |
| void QWinRTLowEnergyServiceHandlerNew::emitErrorAndQuitThread(const QString &error) |
| { |
| emit errorOccured(error); |
| QThread::currentThread()->quit(); |
| } |
| |
| QLowEnergyControllerPrivateWinRTNew::QLowEnergyControllerPrivateWinRTNew() |
| : QLowEnergyControllerPrivate() |
| { |
| registerQLowEnergyControllerMetaType(); |
| connect(this, &QLowEnergyControllerPrivateWinRTNew::characteristicChanged, |
| this, &QLowEnergyControllerPrivateWinRTNew::handleCharacteristicChanged, |
| Qt::QueuedConnection); |
| } |
| |
| QLowEnergyControllerPrivateWinRTNew::~QLowEnergyControllerPrivateWinRTNew() |
| { |
| unregisterFromStatusChanges(); |
| unregisterFromValueChanges(); |
| mAbortPending = true; |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::init() |
| { |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::connectToDevice() |
| { |
| qCDebug(QT_BT_WINRT) << __FUNCTION__; |
| mAbortPending = false; |
| 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); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain device factory", return) |
| ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromIdOperation; |
| hr = deviceStatics->FromBluetoothAddressAsync(remoteDevice.toUInt64(), &deviceFromIdOperation); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not find LE device from address", return) |
| hr = QWinRTFunctions::await(deviceFromIdOperation, mDevice.GetAddressOf(), |
| QWinRTFunctions::ProcessMainThreadEvents, 5000); |
| if (FAILED(hr) || !mDevice) { |
| qCWarning(QT_BT_WINRT) << "Could not find LE device"; |
| setError(QLowEnergyController::InvalidBluetoothAdapterError); |
| setState(QLowEnergyController::UnconnectedState); |
| return; |
| } |
| BluetoothConnectionStatus status; |
| hr = mDevice->get_ConnectionStatus(&status); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain device's connection status", return) |
| if (status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) { |
| setState(QLowEnergyController::ConnectedState); |
| emit q->connected(); |
| return; |
| } |
| |
| QBluetoothLocalDevice localDevice; |
| QBluetoothLocalDevice::Pairing pairing = localDevice.pairingStatus(remoteDevice); |
| if (pairing == QBluetoothLocalDevice::Unpaired) |
| connectToUnpairedDevice(); |
| else |
| connectToPairedDevice(); |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::disconnectFromDevice() |
| { |
| qCDebug(QT_BT_WINRT) << __FUNCTION__; |
| Q_Q(QLowEnergyController); |
| setState(QLowEnergyController::ClosingState); |
| unregisterFromValueChanges(); |
| unregisterFromStatusChanges(); |
| mAbortPending = true; |
| mDevice = nullptr; |
| setState(QLowEnergyController::UnconnectedState); |
| emit q->disconnected(); |
| } |
| |
| ComPtr<IGattDeviceService> QLowEnergyControllerPrivateWinRTNew::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> QLowEnergyControllerPrivateWinRTNew::getNativeCharacteristic( |
| const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid) |
| { |
| ComPtr<IGattDeviceService> service = getNativeService(serviceUuid); |
| if (!service) |
| return nullptr; |
| |
| ComPtr<IGattDeviceService3> service3; |
| HRESULT hr = service.As(&service3); |
| RETURN_IF_FAILED("Could not cast service", return nullptr); |
| |
| ComPtr<IAsyncOperation<GattCharacteristicsResult *>> op; |
| ComPtr<IGattCharacteristicsResult> result; |
| hr = service3->GetCharacteristicsForUuidAsync(charUuid, &op); |
| RETURN_IF_FAILED("Could not obtain native characteristics for service", return nullptr); |
| hr = QWinRTFunctions::await(op, result.GetAddressOf(), QWinRTFunctions::ProcessMainThreadEvents, 5000); |
| RETURN_IF_FAILED("Could not await completion of characteristic operation", return nullptr); |
| GattCommunicationStatus status; |
| hr = result->get_Status(&status); |
| if (FAILED(hr) || status != GattCommunicationStatus_Success) { |
| qErrnoWarning(hr, "Native characteristic operation failed."); |
| return nullptr; |
| } |
| ComPtr<IVectorView<GattCharacteristic *>> characteristics; |
| hr = result->get_Characteristics(&characteristics); |
| RETURN_IF_FAILED("Could not obtain characteristic list.", return nullptr); |
| uint size; |
| hr = characteristics->get_Size(&size); |
| RETURN_IF_FAILED("Could not obtain characteristic list's size.", return nullptr); |
| if (size != 1) |
| qErrnoWarning("More than 1 characteristic found."); |
| ComPtr<IGattCharacteristic> characteristic; |
| hr = characteristics->GetAt(0, &characteristic); |
| RETURN_IF_FAILED("Could not obtain first characteristic for service", return nullptr); |
| return characteristic; |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::registerForValueChanges(const QBluetoothUuid &serviceUuid, |
| const QBluetoothUuid &charUuid) |
| { |
| qCDebug(QT_BT_WINRT) << "Registering characteristic" << charUuid << "in service" |
| << serviceUuid << "for value changes"; |
| for (const ValueChangedEntry &entry : qAsConst(mValueChangedTokens)) { |
| GUID guuid; |
| HRESULT hr; |
| hr = entry.characteristic->get_Uuid(&guuid); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain characteristic's Uuid") |
| if (QBluetoothUuid(guuid) == charUuid) |
| return; |
| } |
| ComPtr<IGattCharacteristic> characteristic = getNativeCharacteristic(serviceUuid, charUuid); |
| if (!characteristic) { |
| qCDebug(QT_BT_WINRT).nospace() << "Could not obtain native characteristic " << charUuid |
| << " from service " << serviceUuid << ". Qt will not be able to signal" |
| << " changes for this characteristic."; |
| return; |
| } |
| |
| EventRegistrationToken token; |
| HRESULT hr; |
| hr = characteristic->add_ValueChanged( |
| Callback<ValueChangedHandler>(this, &QLowEnergyControllerPrivateWinRTNew::onValueChange).Get(), |
| &token); |
| RETURN_IF_FAILED("Could not register characteristic for value changes", return) |
| mValueChangedTokens.append(ValueChangedEntry(characteristic, token)); |
| qCDebug(QT_BT_WINRT) << "Characteristic" << charUuid << "in service" |
| << serviceUuid << "registered for value changes"; |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::unregisterFromValueChanges() |
| { |
| qCDebug(QT_BT_WINRT) << "Unregistering " << mValueChangedTokens.count() << " value change tokens"; |
| HRESULT hr; |
| for (const ValueChangedEntry &entry : qAsConst(mValueChangedTokens)) { |
| if (!entry.characteristic) { |
| qCWarning(QT_BT_WINRT) << "Unregistering from value changes for characteristic failed." |
| << "Characteristic has been deleted"; |
| continue; |
| } |
| hr = entry.characteristic->remove_ValueChanged(entry.token); |
| if (FAILED(hr)) |
| qCWarning(QT_BT_WINRT) << "Unregistering from value changes for characteristic failed."; |
| } |
| mValueChangedTokens.clear(); |
| } |
| |
| HRESULT QLowEnergyControllerPrivateWinRTNew::onValueChange(IGattCharacteristic *characteristic, IGattValueChangedEventArgs *args) |
| { |
| HRESULT hr; |
| quint16 handle; |
| hr = characteristic->get_AttributeHandle(&handle); |
| RETURN_IF_FAILED("Could not obtain characteristic's handle", return S_OK) |
| ComPtr<IBuffer> buffer; |
| hr = args->get_CharacteristicValue(&buffer); |
| RETURN_IF_FAILED("Could not obtain characteristic's value", return S_OK) |
| emit characteristicChanged(handle, byteArrayFromBuffer(buffer)); |
| return S_OK; |
| } |
| |
| bool QLowEnergyControllerPrivateWinRTNew::registerForStatusChanges() |
| { |
| if (!mDevice) |
| return false; |
| |
| qCDebug(QT_BT_WINRT) << __FUNCTION__; |
| |
| HRESULT hr; |
| hr = QEventDispatcherWinRT::runOnXamlThread([this]() { |
| HRESULT hr; |
| hr = mDevice->add_ConnectionStatusChanged( |
| Callback<StatusHandler>(this, &QLowEnergyControllerPrivateWinRTNew::onStatusChange).Get(), |
| &mStatusChangedToken); |
| RETURN_IF_FAILED("Could not register connection status callback", return hr) |
| return S_OK; |
| }); |
| RETURN_FALSE_IF_FAILED("Could not add status callback on Xaml thread") |
| return true; |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::unregisterFromStatusChanges() |
| { |
| qCDebug(QT_BT_WINRT) << __FUNCTION__; |
| if (mDevice && mStatusChangedToken.value) { |
| mDevice->remove_ConnectionStatusChanged(mStatusChangedToken); |
| mStatusChangedToken.value = 0; |
| } |
| } |
| |
| HRESULT QLowEnergyControllerPrivateWinRTNew::onStatusChange(IBluetoothLEDevice *dev, IInspectable *) |
| { |
| Q_Q(QLowEnergyController); |
| BluetoothConnectionStatus status; |
| HRESULT hr; |
| hr = dev->get_ConnectionStatus(&status); |
| RETURN_IF_FAILED("Could not obtain connection status", return S_OK) |
| if (state == QLowEnergyController::ConnectingState |
| && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) { |
| setState(QLowEnergyController::ConnectedState); |
| emit q->connected(); |
| } else if (state != QLowEnergyController::UnconnectedState |
| && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Disconnected) { |
| invalidateServices(); |
| unregisterFromValueChanges(); |
| unregisterFromStatusChanges(); |
| mDevice = nullptr; |
| setError(QLowEnergyController::RemoteHostClosedError); |
| setState(QLowEnergyController::UnconnectedState); |
| emit q->disconnected(); |
| } |
| return S_OK; |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::obtainIncludedServices( |
| QSharedPointer<QLowEnergyServicePrivate> servicePointer, |
| ComPtr<IGattDeviceService> service) |
| { |
| Q_Q(QLowEnergyController); |
| ComPtr<IGattDeviceService3> service3; |
| HRESULT hr = service.As(&service3); |
| RETURN_IF_FAILED("Could not cast service", return); |
| ComPtr<IAsyncOperation<GattDeviceServicesResult *>> op; |
| hr = service3->GetIncludedServicesAsync(&op); |
| // Some devices return ERROR_ACCESS_DISABLED_BY_POLICY |
| RETURN_IF_FAILED("Could not obtain included services", return); |
| ComPtr<IGattDeviceServicesResult> result; |
| hr = QWinRTFunctions::await(op, result.GetAddressOf(), QWinRTFunctions::ProcessMainThreadEvents, 5000); |
| RETURN_IF_FAILED("Could not await service operation", return); |
| GattCommunicationStatus status; |
| hr = result->get_Status(&status); |
| if (FAILED(hr) || status != GattCommunicationStatus_Success) { |
| qErrnoWarning("Could not obtain list of included services"); |
| return; |
| } |
| ComPtr<IVectorView<GattDeviceService *>> includedServices; |
| hr = result->get_Services(&includedServices); |
| RETURN_IF_FAILED("Could not obtain service list", return); |
| |
| uint count; |
| hr = includedServices->get_Size(&count); |
| RETURN_IF_FAILED("Could not obtain service list's size", return); |
| for (uint i = 0; i < count; ++i) { |
| ComPtr<IGattDeviceService> includedService; |
| hr = includedServices->GetAt(i, &includedService); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service from list"); |
| GUID guuid; |
| hr = includedService->get_Uuid(&guuid); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain included service's Uuid"); |
| const QBluetoothUuid includedUuid(guuid); |
| QSharedPointer<QLowEnergyServicePrivate> includedPointer; |
| qCDebug(QT_BT_WINRT_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); |
| } |
| } |
| |
| HRESULT QLowEnergyControllerPrivateWinRTNew::onServiceDiscoveryFinished(ABI::Windows::Foundation::IAsyncOperation<GattDeviceServicesResult *> *op, AsyncStatus status) |
| { |
| Q_Q(QLowEnergyController); |
| if (status != AsyncStatus::Completed) { |
| qCDebug(QT_BT_WINRT) << "Could not obtain services"; |
| return S_OK; |
| } |
| ComPtr<IGattDeviceServicesResult> result; |
| ComPtr<IVectorView<GattDeviceService *>> deviceServices; |
| HRESULT hr = op->GetResults(&result); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service discovery result", |
| return S_OK); |
| GattCommunicationStatus commStatus; |
| hr = result->get_Status(&commStatus); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service discovery status", |
| return S_OK); |
| if (commStatus != GattCommunicationStatus_Success) |
| return S_OK; |
| |
| hr = result->get_Services(&deviceServices); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service list", |
| return S_OK); |
| |
| uint serviceCount; |
| hr = deviceServices->get_Size(&serviceCount); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service list size", |
| return S_OK); |
| for (uint i = 0; i < serviceCount; ++i) { |
| ComPtr<IGattDeviceService> deviceService; |
| hr = deviceServices->GetAt(i, &deviceService); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service"); |
| GUID guuid; |
| hr = deviceService->get_Uuid(&guuid); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service's Uuid"); |
| const QBluetoothUuid service(guuid); |
| |
| qCDebug(QT_BT_WINRT_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); |
| |
| emit q->serviceDiscovered(service); |
| } |
| |
| setState(QLowEnergyController::DiscoveredState); |
| emit q->discoveryFinished(); |
| |
| return S_OK; |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::discoverServices() |
| { |
| qCDebug(QT_BT_WINRT) << "Service discovery initiated"; |
| |
| ComPtr<IBluetoothLEDevice3> device3; |
| HRESULT hr = mDevice.As(&device3); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast device", return); |
| ComPtr<IAsyncOperation<GenericAttributeProfile::GattDeviceServicesResult *>> asyncResult; |
| hr = device3->GetGattServicesAsync(&asyncResult); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain services", return); |
| hr = QEventDispatcherWinRT::runOnXamlThread( [asyncResult, this] () { |
| HRESULT hr = asyncResult->put_Completed( |
| Callback<IAsyncOperationCompletedHandler<GenericAttributeProfile::GattDeviceServicesResult *>>( |
| this, &QLowEnergyControllerPrivateWinRTNew::onServiceDiscoveryFinished).Get()); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not register service discovery callback", |
| return S_OK) |
| return hr; |
| }); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not run registration in Xaml thread", |
| return) |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::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); |
| qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" |
| << QThread::currentThread(); |
| pointer->setState(QLowEnergyService::DiscoveringServices); |
| ComPtr<IGattDeviceService3> deviceService3; |
| HRESULT hr = deviceService.As(&deviceService3); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast service", |
| pointer, QLowEnergyService::UnknownError, return) |
| ComPtr<IAsyncOperation<GattDeviceServicesResult *>> op; |
| hr = deviceService3->GetIncludedServicesAsync(&op); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain included service list", |
| pointer, QLowEnergyService::UnknownError, return) |
| ComPtr<IGattDeviceServicesResult> result; |
| hr = QWinRTFunctions::await(op, result.GetAddressOf()); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not await service operation", |
| pointer, QLowEnergyService::UnknownError, return) |
| GattCommunicationStatus status; |
| hr = result->get_Status(&status); |
| if (FAILED(hr) || status != GattCommunicationStatus_Success) { |
| qCDebug(QT_BT_WINRT) << "Obtaining list of included services failed"; |
| pointer->setError(QLowEnergyService::UnknownError); |
| return; |
| } |
| ComPtr<IVectorView<GattDeviceService *>> deviceServices; |
| hr = result->get_Services(&deviceServices); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain service list from result", |
| pointer, QLowEnergyService::UnknownError, return) |
| uint serviceCount; |
| hr = deviceServices->get_Size(&serviceCount); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain included service list's size", |
| pointer, QLowEnergyService::UnknownError, return) |
| for (uint i = 0; i < serviceCount; ++i) { |
| ComPtr<IGattDeviceService> includedService; |
| hr = deviceServices->GetAt(i, &includedService); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service from list") |
| GUID guuid; |
| hr = includedService->get_Uuid(&guuid); |
| WARN_AND_CONTINUE_IF_FAILED(hr, "Could not obtain service Uuid") |
| |
| const QBluetoothUuid service(guuid); |
| if (service.isNull()) { |
| qCDebug(QT_BT_WINRT) << "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; |
| } |
| |
| QWinRTLowEnergyServiceHandlerNew *worker |
| = new QWinRTLowEnergyServiceHandlerNew(service, deviceService3); |
| QThread *thread = new QThread; |
| worker->moveToThread(thread); |
| connect(thread, &QThread::started, worker, &QWinRTLowEnergyServiceHandlerNew::obtainCharList); |
| connect(thread, &QThread::finished, thread, &QObject::deleteLater); |
| connect(thread, &QThread::finished, worker, &QObject::deleteLater); |
| connect(worker, &QWinRTLowEnergyServiceHandlerNew::errorOccured, |
| this, &QLowEnergyControllerPrivateWinRTNew::handleServiceHandlerError); |
| connect(worker, &QWinRTLowEnergyServiceHandlerNew::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 : qAsConst(indicateChars)) |
| registerForValueChanges(service, indicateChar); |
| return S_OK; |
| }); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register for value changes in Xaml thread", |
| pointer, QLowEnergyService::UnknownError, return) |
| |
| pointer->setState(QLowEnergyService::ServiceDiscovered); |
| thread->exit(0); |
| }); |
| thread->start(); |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::startAdvertising( |
| const QLowEnergyAdvertisingParameters &, |
| const QLowEnergyAdvertisingData &, |
| const QLowEnergyAdvertisingData &) |
| { |
| setError(QLowEnergyController::AdvertisingError); |
| Q_UNIMPLEMENTED(); |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::stopAdvertising() |
| { |
| Q_UNIMPLEMENTED(); |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::requestConnectionUpdate(const QLowEnergyConnectionParameters &) |
| { |
| Q_UNIMPLEMENTED(); |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::readCharacteristic( |
| const QSharedPointer<QLowEnergyServicePrivate> service, |
| const QLowEnergyHandle charHandle) |
| { |
| qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle; |
| qCDebug(QT_BT_WINRT_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_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); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not read characteristic", |
| service, QLowEnergyService::CharacteristicReadError, return S_OK) |
| 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); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain result for characteristic", |
| service, 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()); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register characteristic read callback", |
| service, QLowEnergyService::CharacteristicReadError, return S_OK) |
| return S_OK; |
| }); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not run registration on Xaml thread", |
| service, QLowEnergyService::CharacteristicReadError, return) |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::readDescriptor( |
| const QSharedPointer<QLowEnergyServicePrivate> service, |
| const QLowEnergyHandle charHandle, |
| const QLowEnergyHandle descHandle) |
| { |
| qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle; |
| qCDebug(QT_BT_WINRT_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_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]() { |
| 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::DescriptorReadError); |
| return S_OK; |
| } |
| |
| // Get native descriptor |
| if (!charData.descriptorList.contains(descHandle)) |
| qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "cannot be found in characteristic" << charHandle; |
| const QLowEnergyServicePrivate::DescData descData = charData.descriptorList.value(descHandle); |
| const QBluetoothUuid descUuid = descData.uuid; |
| if (descUuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { |
| ComPtr<IAsyncOperation<ClientCharConfigDescriptorResult *>> readOp; |
| HRESULT hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not read client characteristic configuration", |
| service, QLowEnergyService::DescriptorReadError, return S_OK) |
| auto readCompletedLambda = [charHandle, 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); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain result for descriptor", |
| service, QLowEnergyService::DescriptorReadError, return S_OK) |
| GattClientCharacteristicConfigurationDescriptorValue value; |
| hr = iValue->get_ClientCharacteristicConfigurationDescriptor(&value); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain value for descriptor", |
| service, 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; |
| } |
| QLowEnergyServicePrivate::DescData descData; |
| descData.uuid = QBluetoothUuid::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 S_OK; |
| }; |
| hr = readOp->put_Completed( |
| Callback<IAsyncOperationCompletedHandler<ClientCharConfigDescriptorResult *>>( |
| readCompletedLambda).Get()); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register descriptor read callback", |
| service, QLowEnergyService::DescriptorReadError, return S_OK) |
| return S_OK; |
| } else { |
| ComPtr<IGattCharacteristic3> characteristic3; |
| HRESULT hr = characteristic.As(&characteristic3); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast characteristic", |
| service, QLowEnergyService::DescriptorReadError, return S_OK) |
| ComPtr<IAsyncOperation<GattDescriptorsResult *>> op; |
| hr = characteristic3->GetDescriptorsForUuidAsync(descData.uuid, &op); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descriptor for uuid", |
| service, QLowEnergyService::DescriptorReadError, return S_OK) |
| ComPtr<IGattDescriptorsResult> result; |
| hr = QWinRTFunctions::await(op, result.GetAddressOf(), QWinRTFunctions::ProcessMainThreadEvents, 5000); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not await descritpor read result", |
| service, QLowEnergyService::DescriptorReadError, return S_OK) |
| |
| GattCommunicationStatus commStatus; |
| hr = result->get_Status(&commStatus); |
| if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { |
| qErrnoWarning("Could not obtain list of descriptors"); |
| service->setError(QLowEnergyService::DescriptorReadError); |
| return S_OK; |
| } |
| |
| ComPtr<IVectorView<GattDescriptor *>> descriptors; |
| hr = result->get_Descriptors(&descriptors); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descriptor list", |
| service, QLowEnergyService::DescriptorReadError, return S_OK) |
| uint size; |
| hr = descriptors->get_Size(&size); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not await descritpor list's size", |
| service, QLowEnergyService::DescriptorReadError, return S_OK) |
| if (size == 0) { |
| qCWarning(QT_BT_WINRT) << "No descriptor with uuid" << descData.uuid << "was found."; |
| service->setError(QLowEnergyService::DescriptorReadError); |
| return S_OK; |
| } else if (size > 1) { |
| qCWarning(QT_BT_WINRT) << "There is more than 1 descriptor with uuid" << descData.uuid; |
| } |
| |
| ComPtr<IGattDescriptor> descriptor; |
| hr = descriptors->GetAt(0, &descriptor); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descritpor from list", |
| service, QLowEnergyService::DescriptorReadError, return S_OK) |
| ComPtr<IAsyncOperation<GattReadResult*>> readOp; |
| hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not read descriptor value", |
| service, QLowEnergyService::DescriptorReadError, return S_OK) |
| auto readCompletedLambda = [charHandle, descHandle, descUuid, 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; |
| } |
| QLowEnergyServicePrivate::DescData descData; |
| descData.uuid = descUuid; |
| if (descData.uuid == QBluetoothUuid::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); |
| return S_OK; |
| }; |
| hr = readOp->put_Completed(Callback<IAsyncOperationCompletedHandler<GattReadResult *>>( |
| readCompletedLambda).Get()); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register descriptor read callback", |
| service, QLowEnergyService::DescriptorReadError, return S_OK) |
| return S_OK; |
| } |
| }); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not run registration on Xaml thread", |
| service, QLowEnergyService::DescriptorReadError, return) |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::writeCharacteristic( |
| const QSharedPointer<QLowEnergyServicePrivate> service, |
| const QLowEnergyHandle charHandle, |
| const QByteArray &newValue, |
| QLowEnergyService::WriteMode mode) |
| { |
| qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << newValue << mode; |
| qCDebug(QT_BT_WINRT_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_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); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain buffer factory", |
| service, QLowEnergyService::CharacteristicWriteError, return S_OK) |
| ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; |
| const quint32 length = quint32(newValue.length()); |
| hr = bufferFactory->Create(length, &buffer); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not create buffer", |
| service, QLowEnergyService::CharacteristicWriteError, return S_OK) |
| hr = buffer->put_Length(length); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not set buffer length", |
| service, QLowEnergyService::CharacteristicWriteError, return S_OK) |
| ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess; |
| hr = buffer.As(&byteAccess); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast buffer", |
| service, QLowEnergyService::CharacteristicWriteError, return S_OK) |
| byte *bytes; |
| hr = byteAccess->Buffer(&bytes); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not set buffer", |
| service, QLowEnergyService::CharacteristicWriteError, return S_OK) |
| memcpy(bytes, newValue, length); |
| ComPtr<IAsyncOperation<GattCommunicationStatus>> writeOp; |
| GattWriteOption option = writeWithResponse ? GattWriteOption_WriteWithResponse |
| : GattWriteOption_WriteWithoutResponse; |
| hr = characteristic->WriteValueWithOptionAsync(buffer.Get(), option, &writeOp); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could write characteristic", |
| service, QLowEnergyService::CharacteristicWriteError, return S_OK) |
| QPointer<QLowEnergyControllerPrivateWinRTNew> thisPtr(this); |
| auto writeCompletedLambda = [charData, charHandle, newValue, service, writeWithResponse, thisPtr] |
| (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; |
| } |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain characteristic write result", |
| service, QLowEnergyService::CharacteristicWriteError, return S_OK) |
| 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) |
| thisPtr->updateValueOfCharacteristic(charHandle, newValue, false); |
| if (writeWithResponse) |
| emit service->characteristicWritten(QLowEnergyCharacteristic(service, charHandle), |
| newValue); |
| return S_OK; |
| }; |
| hr = writeOp->put_Completed( |
| Callback<IAsyncOperationCompletedHandler<GattCommunicationStatus>>( |
| writeCompletedLambda).Get()); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register characteristic write callback", |
| service, QLowEnergyService::CharacteristicWriteError, return S_OK) |
| return S_OK; |
| }); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not run registration on Xaml thread", |
| service, QLowEnergyService::CharacteristicWriteError, return) |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::writeDescriptor( |
| const QSharedPointer<QLowEnergyServicePrivate> service, |
| const QLowEnergyHandle charHandle, |
| const QLowEnergyHandle descHandle, |
| const QByteArray &newValue) |
| { |
| qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle << newValue; |
| qCDebug(QT_BT_WINRT_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_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); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not write client characteristic configuration", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| QPointer<QLowEnergyControllerPrivateWinRTNew> thisPtr(this); |
| auto writeCompletedLambda = [charHandle, descHandle, newValue, service, thisPtr] |
| (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); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain result for descriptor", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| if (result != GattCommunicationStatus_Success) { |
| qCWarning(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed"; |
| service->setError(QLowEnergyService::DescriptorWriteError); |
| return S_OK; |
| } |
| thisPtr->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()); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register descriptor write callback", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| } else { |
| ComPtr<IGattCharacteristic3> characteristic3; |
| HRESULT hr = characteristic.As(&characteristic3); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast characteristic", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| ComPtr<IAsyncOperation<GattDescriptorsResult *>> op; |
| hr = characteristic3->GetDescriptorsForUuidAsync(descData.uuid, &op); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descriptor from Uuid", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| ComPtr<IGattDescriptorsResult> result; |
| hr = QWinRTFunctions::await(op, result.GetAddressOf(), QWinRTFunctions::ProcessMainThreadEvents, 5000); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not await descriptor operation", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| GattCommunicationStatus commStatus; |
| hr = result->get_Status(&commStatus); |
| if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { |
| qCWarning(QT_BT_WINRT) << "Descriptor operation failed"; |
| service->setError(QLowEnergyService::DescriptorWriteError); |
| return S_OK; |
| } |
| ComPtr<IVectorView<GattDescriptor *>> descriptors; |
| hr = result->get_Descriptors(&descriptors); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain list of descriptors", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| uint size; |
| hr = descriptors->get_Size(&size); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain list of descriptors' size", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| if (size == 0) { |
| qCWarning(QT_BT_WINRT) << "No descriptor with uuid" << descData.uuid << "was found."; |
| return S_OK; |
| } else if (size > 1) { |
| qCWarning(QT_BT_WINRT) << "There is more than 1 descriptor with uuid" << descData.uuid; |
| } |
| ComPtr<IGattDescriptor> descriptor; |
| hr = descriptors->GetAt(0, &descriptor); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descriptor", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory; |
| hr = GetActivationFactory( |
| HStringReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(), |
| &bufferFactory); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain buffer factory", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; |
| const quint32 length = quint32(newValue.length()); |
| hr = bufferFactory->Create(length, &buffer); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not create buffer", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| hr = buffer->put_Length(length); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not set buffer length", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess; |
| hr = buffer.As(&byteAccess); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast buffer", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| byte *bytes; |
| hr = byteAccess->Buffer(&bytes); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not set buffer", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| memcpy(bytes, newValue, length); |
| ComPtr<IAsyncOperation<GattCommunicationStatus>> writeOp; |
| hr = descriptor->WriteValueAsync(buffer.Get(), &writeOp); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not write descriptor value", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| QPointer<QLowEnergyControllerPrivateWinRTNew> thisPtr(this); |
| auto writeCompletedLambda = [charHandle, descHandle, newValue, service, thisPtr] |
| (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); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain result for descriptor", |
| service, 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; |
| } |
| thisPtr->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()); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register descriptor write callback", |
| service, QLowEnergyService::DescriptorWriteError, return S_OK) |
| return S_OK; |
| } |
| return S_OK; |
| }); |
| CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not run registration on Xaml thread", |
| service, QLowEnergyService::DescriptorWriteError, return) |
| } |
| |
| |
| void QLowEnergyControllerPrivateWinRTNew::addToGenericAttributeList(const QLowEnergyServiceData &, |
| QLowEnergyHandle) |
| { |
| Q_UNIMPLEMENTED(); |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::handleCharacteristicChanged( |
| quint16 charHandle, const QByteArray &data) |
| { |
| qCDebug(QT_BT_WINRT) << __FUNCTION__ << charHandle << data; |
| qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" |
| << QThread::currentThread(); |
| 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); |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::handleServiceHandlerError(const QString &error) |
| { |
| if (state != QLowEnergyController::DiscoveringState) |
| return; |
| |
| qCWarning(QT_BT_WINRT) << "Error while discovering services:" << error; |
| setState(QLowEnergyController::UnconnectedState); |
| setError(QLowEnergyController::ConnectionError); |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::connectToPairedDevice() |
| { |
| Q_Q(QLowEnergyController); |
| ComPtr<IBluetoothLEDevice3> device3; |
| HRESULT hr = mDevice.As(&device3); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast device", return) |
| ComPtr<IAsyncOperation<GattDeviceServicesResult *>> deviceServicesOp; |
| while (!mAbortPending) { |
| hr = device3->GetGattServicesAsync(&deviceServicesOp); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain services", return) |
| ComPtr<IGattDeviceServicesResult> deviceServicesResult; |
| hr = QWinRTFunctions::await(deviceServicesOp, deviceServicesResult.GetAddressOf(), |
| QWinRTFunctions::ProcessThreadEvents, 5000); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not await services operation", return) |
| |
| GattCommunicationStatus commStatus; |
| hr = deviceServicesResult->get_Status(&commStatus); |
| if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { |
| qCWarning(QT_BT_WINRT()) << "Service operation failed"; |
| setError(QLowEnergyController::ConnectionError); |
| setState(QLowEnergyController::UnconnectedState); |
| unregisterFromStatusChanges(); |
| return; |
| } |
| |
| ComPtr<IVectorView <GattDeviceService *>> deviceServices; |
| hr = deviceServicesResult->get_Services(&deviceServices); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain list of services", return) |
| uint serviceCount; |
| hr = deviceServices->get_Size(&serviceCount); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service count", return) |
| |
| if (serviceCount == 0) { |
| qCWarning(QT_BT_WINRT()) << "Found devices without services"; |
| setError(QLowEnergyController::ConnectionError); |
| setState(QLowEnergyController::UnconnectedState); |
| unregisterFromStatusChanges(); |
| return; |
| } |
| |
| // 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) { |
| ComPtr<IGattDeviceService> service; |
| hr = deviceServices->GetAt(i, &service); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service", return); |
| ComPtr<IGattDeviceService3> service3; |
| hr = service.As(&service3); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast service", return); |
| ComPtr<IAsyncOperation<GattCharacteristicsResult *>> characteristicsOp; |
| hr = service3->GetCharacteristicsAsync(&characteristicsOp); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic", return); |
| ComPtr<IGattCharacteristicsResult> characteristicsResult; |
| hr = QWinRTFunctions::await(characteristicsOp, characteristicsResult.GetAddressOf(), |
| QWinRTFunctions::ProcessThreadEvents, 5000); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not await characteristic operation", return); |
| GattCommunicationStatus commStatus; |
| hr = characteristicsResult->get_Status(&commStatus); |
| if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { |
| qCWarning(QT_BT_WINRT) << "Characteristic operation failed"; |
| break; |
| } |
| ComPtr<IVectorView<GattCharacteristic *>> characteristics; |
| hr = characteristicsResult->get_Characteristics(&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); |
| unregisterFromStatusChanges(); |
| return; |
| } |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic list", return); |
| uint characteristicsCount; |
| hr = characteristics->get_Size(&characteristicsCount); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic list's size", return); |
| for (uint j = 0; j < characteristicsCount; ++j) { |
| ComPtr<IGattCharacteristic> characteristic; |
| hr = characteristics->GetAt(j, &characteristic); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic", return); |
| ComPtr<IAsyncOperation<GattReadResult *>> op; |
| GattCharacteristicProperties props; |
| hr = characteristic->get_CharacteristicProperties(&props); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic's properties", return); |
| if (!(props & GattCharacteristicProperties_Read)) |
| continue; |
| hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode::BluetoothCacheMode_Uncached, &op); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not read characteristic value", return); |
| ComPtr<IGattReadResult> result; |
| hr = QWinRTFunctions::await(op, result.GetAddressOf(), QWinRTFunctions::ProcessThreadEvents, 500); |
| // 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. |
| if (hr == E_ILLEGAL_METHOD_CALL) |
| break; |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not await characteristic read", return); |
| ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; |
| hr = result->get_Value(&buffer); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic value", return); |
| if (!buffer) { |
| qCDebug(QT_BT_WINRT) << "Problem reading value"; |
| break; |
| } |
| |
| setState(QLowEnergyController::ConnectedState); |
| emit q->connected(); |
| if (!registerForStatusChanges()) { |
| setError(QLowEnergyController::ConnectionError); |
| setState(QLowEnergyController::UnconnectedState); |
| return; |
| } |
| return; |
| } |
| } |
| } |
| } |
| |
| void QLowEnergyControllerPrivateWinRTNew::connectToUnpairedDevice() |
| { |
| if (!registerForStatusChanges()) { |
| setError(QLowEnergyController::ConnectionError); |
| setState(QLowEnergyController::UnconnectedState); |
| return; |
| } |
| ComPtr<IBluetoothLEDevice3> device3; |
| HRESULT hr = mDevice.As(&device3); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast device", return) |
| ComPtr<IGattDeviceServicesResult> deviceServicesResult; |
| while (!mAbortPending) { |
| ComPtr<IAsyncOperation<GattDeviceServicesResult *>> deviceServicesOp; |
| hr = device3->GetGattServicesAsync(&deviceServicesOp); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain services", return) |
| hr = QWinRTFunctions::await(deviceServicesOp, deviceServicesResult.GetAddressOf(), |
| QWinRTFunctions::ProcessMainThreadEvents); |
| CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not await services operation", return) |
| |
| GattCommunicationStatus commStatus; |
| hr = deviceServicesResult->get_Status(&commStatus); |
| if (commStatus == GattCommunicationStatus_Unreachable) |
| continue; |
| |
| if (FAILED(hr) || commStatus != GattCommunicationStatus_Success) { |
| qCWarning(QT_BT_WINRT()) << "Service operation failed"; |
| setError(QLowEnergyController::ConnectionError); |
| setState(QLowEnergyController::UnconnectedState); |
| unregisterFromStatusChanges(); |
| return; |
| } |
| |
| break; |
| } |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "qlowenergycontroller_winrt_new.moc" |