/**************************************************************************** ** ** 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 #include #include #include #ifdef CLASSIC_APP_BUILD #define Q_OS_WINRT #endif #include #include #include #include #include #include #include #include #include #include #include #include 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 StatusHandler; typedef ITypedEventHandler 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 &gattResult, bool isWCharString = false) { ComPtr 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 &deviceService) : mService(service) , mDeviceService(deviceService) { qCDebug(QT_BT_WINRT) << __FUNCTION__; } ~QWinRTLowEnergyServiceHandlerNew() { } public slots: void obtainCharList() { mIndicateChars.clear(); qCDebug(QT_BT_WINRT) << __FUNCTION__; ComPtr> characteristicsOp; ComPtr 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> 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 characteristic; hr = characteristics->GetAt(i, &characteristic); if (FAILED(hr)) { qCWarning(QT_BT_WINRT) << "Could not obtain characteristic at" << i; --mCharacteristicsCountToBeDiscovered; continue; } ComPtr 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> 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>( [this, characteristic] (IAsyncOperation *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> readOp; hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp); if (FAILED(hr)) { qCWarning(QT_BT_WINRT) << "Could not read characteristic"; --mCharacteristicsCountToBeDiscovered; checkAllCharacteristicsDiscovered(); return S_OK; } ComPtr 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> descriptors; ComPtr 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 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> readOp; hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp); WARN_AND_CONTINUE_IF_FAILED(hr, "Could not read descriptor value") ComPtr 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> readOp; hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp); WARN_AND_CONTINUE_IF_FAILED(hr, "Could not read descriptor value") ComPtr 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 mDeviceService; QHash mCharacteristicList; uint mCharacteristicsCountToBeDiscovered; quint16 mStartHandle = 0; quint16 mEndHandle = 0; QVector mIndicateChars; signals: void charListObtained(const QBluetoothUuid &service, QHash charList, QVector 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 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> 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 QLowEnergyControllerPrivateWinRTNew::getNativeService( const QBluetoothUuid &serviceUuid) { ComPtr 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 QLowEnergyControllerPrivateWinRTNew::getNativeCharacteristic( const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid) { ComPtr service = getNativeService(serviceUuid); if (!service) return nullptr; ComPtr service3; HRESULT hr = service.As(&service3); RETURN_IF_FAILED("Could not cast service", return nullptr); ComPtr> op; ComPtr 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> 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 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 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(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 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(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 servicePointer, ComPtr service) { Q_Q(QLowEnergyController); ComPtr service3; HRESULT hr = service.As(&service3); RETURN_IF_FAILED("Could not cast service", return); ComPtr> op; hr = service3->GetIncludedServicesAsync(&op); // Some devices return ERROR_ACCESS_DISABLED_BY_POLICY RETURN_IF_FAILED("Could not obtain included services", return); ComPtr 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> 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 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 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(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 *op, AsyncStatus status) { Q_Q(QLowEnergyController); if (status != AsyncStatus::Completed) { qCDebug(QT_BT_WINRT) << "Could not obtain services"; return S_OK; } ComPtr result; ComPtr> 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 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 pointer; if (serviceList.contains(service)) { pointer = serviceList.value(service); } else { QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate(); priv->uuid = service; priv->setController(this); pointer = QSharedPointer(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 device3; HRESULT hr = mDevice.As(&device3); CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast device", return); ComPtr> 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>( 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 deviceService = getNativeService(service); if (!deviceService) { qCDebug(QT_BT_WINRT) << "Could not obtain native service for uuid " << service; return; } //update service data QSharedPointer pointer = serviceList.value(service); qCDebug(QT_BT_WINRT_SERVICE_THREAD) << __FUNCTION__ << "Changing service pointer from thread" << QThread::currentThread(); pointer->setState(QLowEnergyService::DiscoveringServices); ComPtr deviceService3; HRESULT hr = deviceService.As(&deviceService3); CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast service", pointer, QLowEnergyService::UnknownError, return) ComPtr> op; hr = deviceService3->GetIncludedServicesAsync(&op); CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain included service list", pointer, QLowEnergyService::UnknownError, return) ComPtr 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> 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 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 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 charList, QVector indicateChars, QLowEnergyHandle startHandle, QLowEnergyHandle endHandle) { if (!serviceList.contains(service)) { qCWarning(QT_BT_WINRT) << "Discovery done of unknown service:" << service.toString(); return; } QSharedPointer 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 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 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> 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 *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 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>( 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 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 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> 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 *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 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>( 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 characteristic3; HRESULT hr = characteristic.As(&characteristic3); CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast characteristic", service, QLowEnergyService::DescriptorReadError, return S_OK) ComPtr> 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 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> 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 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> 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 *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 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>( 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 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 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 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 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 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> 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 thisPtr(this); auto writeCompletedLambda = [charData, charHandle, newValue, service, writeWithResponse, thisPtr] (IAsyncOperation *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>( 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 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 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(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> 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 thisPtr(this); auto writeCompletedLambda = [charHandle, descHandle, newValue, service, thisPtr] (IAsyncOperation *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>( writeCompletedLambda).Get()); CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not register descriptor write callback", service, QLowEnergyService::DescriptorWriteError, return S_OK) } else { ComPtr characteristic3; HRESULT hr = characteristic.As(&characteristic3); CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not cast characteristic", service, QLowEnergyService::DescriptorWriteError, return S_OK) ComPtr> 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 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> 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 descriptor; hr = descriptors->GetAt(0, &descriptor); CHECK_HR_AND_SET_SERVICE_ERROR(hr, "Could not obtain descriptor", service, QLowEnergyService::DescriptorWriteError, return S_OK) ComPtr 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 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 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> 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 thisPtr(this); auto writeCompletedLambda = [charHandle, descHandle, newValue, service, thisPtr] (IAsyncOperation *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>( 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 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 device3; HRESULT hr = mDevice.As(&device3); CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast device", return) ComPtr> deviceServicesOp; while (!mAbortPending) { hr = device3->GetGattServicesAsync(&deviceServicesOp); CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain services", return) ComPtr 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> 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 service; hr = deviceServices->GetAt(i, &service); CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain service", return); ComPtr service3; hr = service.As(&service3); CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast service", return); ComPtr> characteristicsOp; hr = service3->GetCharacteristicsAsync(&characteristicsOp); CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic", return); ComPtr 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> 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 characteristic; hr = characteristics->GetAt(j, &characteristic); CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not obtain characteristic", return); ComPtr> 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 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 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 device3; HRESULT hr = mDevice.As(&device3); CHECK_FOR_DEVICE_CONNECTION_ERROR(hr, "Could not cast device", return) ComPtr deviceServicesResult; while (!mAbortPending) { ComPtr> 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"