/**************************************************************************** ** ** 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_p.h" #include #include #ifdef CLASSIC_APP_BUILD #define Q_OS_WINRT #endif #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::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; Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) static QVector getIncludedServiceIds(const ComPtr &service) { QVector result; ComPtr service2; HRESULT hr = service.As(&service2); Q_ASSERT_SUCCEEDED(hr); ComPtr> includedServices; hr = service2->GetAllIncludedServices(&includedServices); Q_ASSERT_SUCCEEDED(hr); uint count; hr = includedServices->get_Size(&count); Q_ASSERT_SUCCEEDED(hr); for (uint i = 0; i < count; ++i) { ComPtr includedService; hr = includedServices->GetAt(i, &includedService); Q_ASSERT_SUCCEEDED(hr); GUID guuid; hr = includedService->get_Uuid(&guuid); Q_ASSERT_SUCCEEDED(hr); const QBluetoothUuid service(guuid); result << service; result << getIncludedServiceIds(includedService); } return result; } static QByteArray byteArrayFromBuffer(const ComPtr &buffer, bool isWCharString = false) { ComPtr byteAccess; HRESULT hr = buffer.As(&byteAccess); Q_ASSERT_SUCCEEDED(hr); char *data; hr = byteAccess->Buffer(reinterpret_cast(&data)); Q_ASSERT_SUCCEEDED(hr); UINT32 size; hr = buffer->get_Length(&size); Q_ASSERT_SUCCEEDED(hr); if (isWCharString) { QString valueString = QString::fromUtf16(reinterpret_cast(data)).left(size / 2); return valueString.toUtf8(); } return QByteArray(data, size); } static QByteArray byteArrayFromGattResult(const ComPtr &gattResult, bool isWCharString = false) { ComPtr buffer; HRESULT hr; hr = gattResult->get_Value(&buffer); Q_ASSERT_SUCCEEDED(hr); return byteArrayFromBuffer(buffer, isWCharString); } class QWinRTLowEnergyServiceHandler : public QObject { Q_OBJECT public: QWinRTLowEnergyServiceHandler(const QBluetoothUuid &service, const ComPtr &deviceService) : mService(service) , mDeviceService(deviceService) { qCDebug(QT_BT_WINRT) << __FUNCTION__; } ~QWinRTLowEnergyServiceHandler() { } public slots: void obtainCharList() { QVector indicateChars; quint16 startHandle = 0; quint16 endHandle = 0; qCDebug(QT_BT_WINRT) << __FUNCTION__; ComPtr> characteristics; HRESULT hr = mDeviceService->GetAllCharacteristics(&characteristics); Q_ASSERT_SUCCEEDED(hr); if (!characteristics) { emit charListObtained(mService, mCharacteristicList, indicateChars, startHandle, endHandle); QThread::currentThread()->quit(); return; } uint characteristicsCount; hr = characteristics->get_Size(&characteristicsCount); Q_ASSERT_SUCCEEDED(hr); for (uint i = 0; i < characteristicsCount; ++i) { ComPtr characteristic; hr = characteristics->GetAt(i, &characteristic); Q_ASSERT_SUCCEEDED(hr); quint16 handle; hr = characteristic->get_AttributeHandle(&handle); Q_ASSERT_SUCCEEDED(hr); QLowEnergyServicePrivate::CharData charData; charData.valueHandle = handle + 1; if (startHandle == 0 || startHandle > handle) startHandle = handle; if (endHandle == 0 || endHandle < handle) endHandle = handle; GUID guuid; hr = characteristic->get_Uuid(&guuid); Q_ASSERT_SUCCEEDED(hr); charData.uuid = QBluetoothUuid(guuid); GattCharacteristicProperties properties; hr = characteristic->get_CharacteristicProperties(&properties); Q_ASSERT_SUCCEEDED(hr); charData.properties = QLowEnergyCharacteristic::PropertyTypes(properties); if (charData.properties & QLowEnergyCharacteristic::Read) { ComPtr> readOp; hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp); Q_ASSERT_SUCCEEDED(hr); ComPtr readResult; hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); Q_ASSERT_SUCCEEDED(hr); if (readResult) charData.value = byteArrayFromGattResult(readResult); } ComPtr characteristic2; hr = characteristic.As(&characteristic2); Q_ASSERT_SUCCEEDED(hr); ComPtr> descriptors; hr = characteristic2->GetAllDescriptors(&descriptors); Q_ASSERT_SUCCEEDED(hr); uint descriptorCount; hr = descriptors->get_Size(&descriptorCount); Q_ASSERT_SUCCEEDED(hr); for (uint j = 0; j < descriptorCount; ++j) { QLowEnergyServicePrivate::DescData descData; ComPtr descriptor; hr = descriptors->GetAt(j, &descriptor); Q_ASSERT_SUCCEEDED(hr); quint16 descHandle; hr = descriptor->get_AttributeHandle(&descHandle); Q_ASSERT_SUCCEEDED(hr); GUID descriptorUuid; hr = descriptor->get_Uuid(&descriptorUuid); Q_ASSERT_SUCCEEDED(hr); descData.uuid = QBluetoothUuid(descriptorUuid); if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { ComPtr> readOp; hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp); Q_ASSERT_SUCCEEDED(hr); ComPtr readResult; hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); Q_ASSERT_SUCCEEDED(hr); GattClientCharacteristicConfigurationDescriptorValue value; hr = readResult->get_ClientCharacteristicConfigurationDescriptor(&value); Q_ASSERT_SUCCEEDED(hr); quint16 result = 0; bool correct = false; if (value & GattClientCharacteristicConfigurationDescriptorValue_Indicate) { result |= GattClientCharacteristicConfigurationDescriptorValue_Indicate; correct = true; } if (value & GattClientCharacteristicConfigurationDescriptorValue_Notify) { result |= GattClientCharacteristicConfigurationDescriptorValue_Notify; correct = true; } if (value == GattClientCharacteristicConfigurationDescriptorValue_None) { correct = true; } if (!correct) continue; descData.value = QByteArray(2, Qt::Uninitialized); qToLittleEndian(result, descData.value.data()); indicateChars << charData.uuid; } else { ComPtr> readOp; hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp); Q_ASSERT_SUCCEEDED(hr); ComPtr readResult; hr = QWinRTFunctions::await(readOp, readResult.GetAddressOf()); Q_ASSERT_SUCCEEDED(hr); if (descData.uuid == QBluetoothUuid::CharacteristicUserDescription) descData.value = byteArrayFromGattResult(readResult, true); else descData.value = byteArrayFromGattResult(readResult); } charData.descriptorList.insert(descHandle, descData); } mCharacteristicList.insert(handle, charData); } emit charListObtained(mService, mCharacteristicList, indicateChars, startHandle, endHandle); QThread::currentThread()->quit(); } public: QBluetoothUuid mService; ComPtr mDeviceService; QHash mCharacteristicList; signals: void charListObtained(const QBluetoothUuid &service, QHash charList, QVector indicateChars, QLowEnergyHandle startHandle, QLowEnergyHandle endHandle); }; QLowEnergyControllerPrivateWinRT::QLowEnergyControllerPrivateWinRT() : QLowEnergyControllerPrivate() { qCDebug(QT_BT_WINRT) << __FUNCTION__; registerQLowEnergyControllerMetaType(); } QLowEnergyControllerPrivateWinRT::~QLowEnergyControllerPrivateWinRT() { if (mDevice && mStatusChangedToken.value) mDevice->remove_ConnectionStatusChanged(mStatusChangedToken); qCDebug(QT_BT_WINRT) << "Unregistering " << mValueChangedTokens.count() << " value change tokens"; for (const ValueChangedEntry &entry : qAsConst(mValueChangedTokens)) entry.characteristic->remove_ValueChanged(entry.token); } void QLowEnergyControllerPrivateWinRT::init() { } void QLowEnergyControllerPrivateWinRT::connectToDevice() { qCDebug(QT_BT_WINRT) << __FUNCTION__; Q_Q(QLowEnergyController); if (remoteDevice.isNull()) { qWarning() << "Invalid/null remote device address"; setError(QLowEnergyController::UnknownRemoteDeviceError); return; } setState(QLowEnergyController::ConnectingState); ComPtr deviceStatics; HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice).Get(), &deviceStatics); Q_ASSERT_SUCCEEDED(hr); ComPtr> deviceFromIdOperation; hr = deviceStatics->FromBluetoothAddressAsync(remoteDevice.toUInt64(), &deviceFromIdOperation); Q_ASSERT_SUCCEEDED(hr); hr = QWinRTFunctions::await(deviceFromIdOperation, mDevice.GetAddressOf()); Q_ASSERT_SUCCEEDED(hr); if (!mDevice) { qCDebug(QT_BT_WINRT) << "Could not find LE device"; setError(QLowEnergyController::InvalidBluetoothAdapterError); setState(QLowEnergyController::UnconnectedState); } BluetoothConnectionStatus status; hr = mDevice->get_ConnectionStatus(&status); Q_ASSERT_SUCCEEDED(hr); hr = QEventDispatcherWinRT::runOnXamlThread([this, q]() { HRESULT hr; hr = mDevice->add_ConnectionStatusChanged(Callback([this, q](IBluetoothLEDevice *dev, IInspectable *) { BluetoothConnectionStatus status; HRESULT hr; hr = dev->get_ConnectionStatus(&status); Q_ASSERT_SUCCEEDED(hr); if (state == QLowEnergyController::ConnectingState && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) { setState(QLowEnergyController::ConnectedState); emit q->connected(); } else if (state == QLowEnergyController::ConnectedState && status == BluetoothConnectionStatus::BluetoothConnectionStatus_Disconnected) { setError(QLowEnergyController::RemoteHostClosedError); setState(QLowEnergyController::UnconnectedState); emit q->disconnected(); } return S_OK; }).Get(), &mStatusChangedToken); Q_ASSERT_SUCCEEDED(hr); return S_OK; }); Q_ASSERT_SUCCEEDED(hr); if (status == BluetoothConnectionStatus::BluetoothConnectionStatus_Connected) { setState(QLowEnergyController::ConnectedState); emit q->connected(); return; } ComPtr> deviceServices; hr = mDevice->get_GattServices(&deviceServices); Q_ASSERT_SUCCEEDED(hr); uint serviceCount; hr = deviceServices->get_Size(&serviceCount); Q_ASSERT_SUCCEEDED(hr); // Windows Phone automatically connects to the device as soon as a service value is read/written. // Thus we read one value in order to establish the connection. for (uint i = 0; i < serviceCount; ++i) { ComPtr service; hr = deviceServices->GetAt(i, &service); Q_ASSERT_SUCCEEDED(hr); ComPtr service2; hr = service.As(&service2); Q_ASSERT_SUCCEEDED(hr); ComPtr> characteristics; hr = service2->GetAllCharacteristics(&characteristics); if (hr == E_ACCESSDENIED) { // Everything will work as expected up until this point if the manifest capabilties // for bluetooth LE are not set. qCWarning(QT_BT_WINRT) << "Could not obtain characteristic list. Please check your " "manifest capabilities"; setState(QLowEnergyController::UnconnectedState); setError(QLowEnergyController::ConnectionError); return; } else { Q_ASSERT_SUCCEEDED(hr); } uint characteristicsCount; hr = characteristics->get_Size(&characteristicsCount); Q_ASSERT_SUCCEEDED(hr); for (uint j = 0; j < characteristicsCount; ++j) { ComPtr characteristic; hr = characteristics->GetAt(j, &characteristic); Q_ASSERT_SUCCEEDED(hr); ComPtr> op; GattCharacteristicProperties props; hr = characteristic->get_CharacteristicProperties(&props); Q_ASSERT_SUCCEEDED(hr); if (!(props & GattCharacteristicProperties_Read)) continue; hr = characteristic->ReadValueWithCacheModeAsync(BluetoothCacheMode::BluetoothCacheMode_Uncached, &op); Q_ASSERT_SUCCEEDED(hr); ComPtr result; hr = QWinRTFunctions::await(op, result.GetAddressOf()); if (hr == E_INVALIDARG) { // E_INVALIDARG happens when user tries to connect to a device that was paired // before but is not available. qCDebug(QT_BT_WINRT) << "Could not obtain characteristic read result that triggers" "device connection. Is the device reachable?"; setError(QLowEnergyController::ConnectionError); setState(QLowEnergyController::UnconnectedState); return; } else if (hr != S_OK) { qCWarning(QT_BT_WINRT) << "Connecting to device failed: " << qt_error_string(hr); setError(QLowEnergyController::ConnectionError); setState(QLowEnergyController::UnconnectedState); return; } ComPtr buffer; hr = result->get_Value(&buffer); Q_ASSERT_SUCCEEDED(hr); if (!buffer) { qCDebug(QT_BT_WINRT) << "Problem reading value"; setError(QLowEnergyController::ConnectionError); setState(QLowEnergyController::UnconnectedState); } return; } } } void QLowEnergyControllerPrivateWinRT::disconnectFromDevice() { qCDebug(QT_BT_WINRT) << __FUNCTION__; Q_Q(QLowEnergyController); setState(QLowEnergyController::UnconnectedState); emit q->disconnected(); if (mDevice && mStatusChangedToken.value) { mDevice->remove_ConnectionStatusChanged(mStatusChangedToken); mStatusChangedToken.value = 0; } } ComPtr QLowEnergyControllerPrivateWinRT::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 QLowEnergyControllerPrivateWinRT::getNativeCharacteristic(const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid) { ComPtr service = getNativeService(serviceUuid); if (!service) return nullptr; ComPtr> characteristics; HRESULT hr = service->GetCharacteristics(charUuid, &characteristics); RETURN_IF_FAILED("Could not obtain native characteristics for service", return nullptr); ComPtr characteristic; hr = characteristics->GetAt(0, &characteristic); RETURN_IF_FAILED("Could not obtain first characteristic for service", return nullptr); return characteristic; } void QLowEnergyControllerPrivateWinRT::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); Q_ASSERT_SUCCEEDED(hr); if (QBluetoothUuid(guuid) == charUuid) return; } ComPtr characteristic = getNativeCharacteristic(serviceUuid, charUuid); EventRegistrationToken token; HRESULT hr; hr = characteristic->add_ValueChanged(Callback([this](IGattCharacteristic *characteristic, IGattValueChangedEventArgs *args) { HRESULT hr; quint16 handle; hr = characteristic->get_AttributeHandle(&handle); Q_ASSERT_SUCCEEDED(hr); ComPtr buffer; hr = args->get_CharacteristicValue(&buffer); Q_ASSERT_SUCCEEDED(hr); characteristicChanged(handle, byteArrayFromBuffer(buffer)); return S_OK; }).Get(), &token); Q_ASSERT_SUCCEEDED(hr); mValueChangedTokens.append(ValueChangedEntry(characteristic, token)); qCDebug(QT_BT_WINRT) << "Characteristic" << charUuid << "in service" << serviceUuid << "registered for value changes"; } void QLowEnergyControllerPrivateWinRT::obtainIncludedServices(QSharedPointer servicePointer, ComPtr service) { Q_Q(QLowEnergyController); ComPtr service2; HRESULT hr = service.As(&service2); Q_ASSERT_SUCCEEDED(hr); ComPtr> includedServices; hr = service2->GetAllIncludedServices(&includedServices); // Some devices return ERROR_ACCESS_DISABLED_BY_POLICY if (FAILED(hr)) return; uint count; hr = includedServices->get_Size(&count); Q_ASSERT_SUCCEEDED(hr); for (uint i = 0; i < count; ++i) { ComPtr includedService; hr = includedServices->GetAt(i, &includedService); Q_ASSERT_SUCCEEDED(hr); GUID guuid; hr = includedService->get_Uuid(&guuid); Q_ASSERT_SUCCEEDED(hr); const QBluetoothUuid includedUuid(guuid); QSharedPointer includedPointer; 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); } } void QLowEnergyControllerPrivateWinRT::discoverServices() { Q_Q(QLowEnergyController); qCDebug(QT_BT_WINRT) << "Service discovery initiated"; ComPtr> deviceServices; HRESULT hr = mDevice->get_GattServices(&deviceServices); Q_ASSERT_SUCCEEDED(hr); uint serviceCount; hr = deviceServices->get_Size(&serviceCount); Q_ASSERT_SUCCEEDED(hr); for (uint i = 0; i < serviceCount; ++i) { ComPtr deviceService; hr = deviceServices->GetAt(i, &deviceService); Q_ASSERT_SUCCEEDED(hr); GUID guuid; hr = deviceService->get_Uuid(&guuid); Q_ASSERT_SUCCEEDED(hr); const QBluetoothUuid service(guuid); QSharedPointer 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(); } void QLowEnergyControllerPrivateWinRT::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); pointer->setState(QLowEnergyService::DiscoveringServices); ComPtr deviceService2; HRESULT hr = deviceService.As(&deviceService2); Q_ASSERT_SUCCEEDED(hr); ComPtr> deviceServices; hr = deviceService2->GetAllIncludedServices(&deviceServices); if (FAILED(hr)) { // ERROR_ACCESS_DISABLED_BY_POLICY qCDebug(QT_BT_WINRT) << "Could not obtain included services list for" << service; pointer->setError(QLowEnergyService::UnknownError); pointer->setState(QLowEnergyService::InvalidService); return; } uint serviceCount; hr = deviceServices->get_Size(&serviceCount); Q_ASSERT_SUCCEEDED(hr); for (uint i = 0; i < serviceCount; ++i) { ComPtr includedService; hr = deviceServices->GetAt(i, &includedService); Q_ASSERT_SUCCEEDED(hr); GUID guuid; hr = includedService->get_Uuid(&guuid); Q_ASSERT_SUCCEEDED(hr); const QBluetoothUuid service(guuid); if (service.isNull()) { qCDebug(QT_BT_WINRT) << "Could not find service"; return; } pointer->includedServices.append(service); // update the type of the included service QSharedPointer otherService = serviceList.value(service); if (!otherService.isNull()) otherService->type |= QLowEnergyService::IncludedService; } QWinRTLowEnergyServiceHandler *worker = new QWinRTLowEnergyServiceHandler(service, deviceService2); QThread *thread = new QThread; worker->moveToThread(thread); connect(thread, &QThread::started, worker, &QWinRTLowEnergyServiceHandler::obtainCharList); connect(thread, &QThread::finished, thread, &QObject::deleteLater); connect(thread, &QThread::finished, worker, &QObject::deleteLater); connect(worker, &QWinRTLowEnergyServiceHandler::charListObtained, [this, thread](const QBluetoothUuid &service, QHash 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; }); Q_ASSERT_SUCCEEDED(hr); pointer->setState(QLowEnergyService::ServiceDiscovered); thread->exit(0); }); thread->start(); } void QLowEnergyControllerPrivateWinRT::startAdvertising(const QLowEnergyAdvertisingParameters &, const QLowEnergyAdvertisingData &, const QLowEnergyAdvertisingData &) { setError(QLowEnergyController::AdvertisingError); Q_UNIMPLEMENTED(); } void QLowEnergyControllerPrivateWinRT::stopAdvertising() { Q_UNIMPLEMENTED(); } void QLowEnergyControllerPrivateWinRT::requestConnectionUpdate(const QLowEnergyConnectionParameters &) { Q_UNIMPLEMENTED(); } void QLowEnergyControllerPrivateWinRT::readCharacteristic(const QSharedPointer service, const QLowEnergyHandle charHandle) { qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle; 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); Q_ASSERT_SUCCEEDED(hr); 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); if (FAILED(hr)) { qCDebug(QT_BT_WINRT) << "Could not obtain result for characteristic" << charHandle; service->setError(QLowEnergyService::CharacteristicReadError); return S_OK; } const QByteArray value = byteArrayFromGattResult(characteristicValue); QLowEnergyServicePrivate::CharData charData = service->characteristicList.value(charHandle); charData.value = value; service->characteristicList.insert(charHandle, charData); emit service->characteristicRead(QLowEnergyCharacteristic(service, charHandle), value); return S_OK; }; hr = readOp->put_Completed(Callback>(readCompletedLambda).Get()); Q_ASSERT_SUCCEEDED(hr); return S_OK; }); Q_ASSERT_SUCCEEDED(hr); } void QLowEnergyControllerPrivateWinRT::readDescriptor(const QSharedPointer service, const QLowEnergyHandle charHandle, const QLowEnergyHandle descHandle) { qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle; 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]() { 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; QLowEnergyServicePrivate::DescData descData = charData.descriptorList.value(descHandle); if (descData.uuid == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) { ComPtr> readOp; HRESULT hr = characteristic->ReadClientCharacteristicConfigurationDescriptorAsync(&readOp); Q_ASSERT_SUCCEEDED(hr); auto readCompletedLambda = [&charData, charHandle, &descData, 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); if (FAILED(hr)) { qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle; service->setError(QLowEnergyService::DescriptorReadError); return S_OK; } GattClientCharacteristicConfigurationDescriptorValue value; hr = iValue->get_ClientCharacteristicConfigurationDescriptor(&value); if (FAILED(hr)) { qCDebug(QT_BT_WINRT) << "Could not obtain value for descriptor" << descHandle; service->setError(QLowEnergyService::DescriptorReadError); return S_OK; } quint16 result = 0; bool correct = false; if (value & GattClientCharacteristicConfigurationDescriptorValue_Indicate) { result |= QLowEnergyCharacteristic::Indicate; correct = true; } if (value & GattClientCharacteristicConfigurationDescriptorValue_Notify) { result |= QLowEnergyCharacteristic::Notify; correct = true; } if (value == GattClientCharacteristicConfigurationDescriptorValue_None) correct = true; if (!correct) { qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "read operation failed. Obtained unexpected value."; service->setError(QLowEnergyService::DescriptorReadError); return S_OK; } descData.value = QByteArray(2, Qt::Uninitialized); qToLittleEndian(result, descData.value.data()); charData.descriptorList.insert(descHandle, descData); emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle), descData.value); return S_OK; }; hr = readOp->put_Completed(Callback>(readCompletedLambda).Get()); Q_ASSERT_SUCCEEDED(hr); return S_OK; } else { ComPtr> descriptors; HRESULT hr = characteristic->GetDescriptors(descData.uuid, &descriptors); Q_ASSERT_SUCCEEDED(hr); ComPtr descriptor; hr = descriptors->GetAt(0, &descriptor); Q_ASSERT_SUCCEEDED(hr); ComPtr> readOp; hr = descriptor->ReadValueWithCacheModeAsync(BluetoothCacheMode_Uncached, &readOp); Q_ASSERT_SUCCEEDED(hr); auto readCompletedLambda = [&charData, charHandle, &descData, 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 descriptorValue; HRESULT hr; hr = op->GetResults(&descriptorValue); if (FAILED(hr)) { qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle; service->setError(QLowEnergyService::DescriptorReadError); return S_OK; } if (descData.uuid == QBluetoothUuid::CharacteristicUserDescription) descData.value = byteArrayFromGattResult(descriptorValue, true); else descData.value = byteArrayFromGattResult(descriptorValue); charData.descriptorList.insert(descHandle, descData); emit service->descriptorRead(QLowEnergyDescriptor(service, charHandle, descHandle), descData.value); return S_OK; }; hr = readOp->put_Completed(Callback>(readCompletedLambda).Get()); return S_OK; } }); Q_ASSERT_SUCCEEDED(hr); } void QLowEnergyControllerPrivateWinRT::writeCharacteristic(const QSharedPointer service, const QLowEnergyHandle charHandle, const QByteArray &newValue, QLowEnergyService::WriteMode mode) { qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << newValue << mode; 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); Q_ASSERT_SUCCEEDED(hr); ComPtr buffer; const int length = newValue.length(); hr = bufferFactory->Create(length, &buffer); Q_ASSERT_SUCCEEDED(hr); hr = buffer->put_Length(length); Q_ASSERT_SUCCEEDED(hr); ComPtr byteAccess; hr = buffer.As(&byteAccess); Q_ASSERT_SUCCEEDED(hr); byte *bytes; hr = byteAccess->Buffer(&bytes); Q_ASSERT_SUCCEEDED(hr); memcpy(bytes, newValue, length); ComPtr> writeOp; GattWriteOption option = writeWithResponse ? GattWriteOption_WriteWithResponse : GattWriteOption_WriteWithoutResponse; hr = characteristic->WriteValueWithOptionAsync(buffer.Get(), option, &writeOp); Q_ASSERT_SUCCEEDED(hr); auto writeCompletedLambda =[charData, charHandle, newValue, service, writeWithResponse, this] (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; } Q_ASSERT_SUCCEEDED(hr); if (result != GattCommunicationStatus_Success) { qCDebug(QT_BT_WINRT) << "Characteristic" << charHandle << "write operation failed"; service->setError(QLowEnergyService::CharacteristicWriteError); return S_OK; } // only update cache when property is readable. Otherwise it remains // empty. if (charData.properties & QLowEnergyCharacteristic::Read) updateValueOfCharacteristic(charHandle, newValue, false); if (writeWithResponse) emit service->characteristicWritten(QLowEnergyCharacteristic(service, charHandle), newValue); return S_OK; }; hr = writeOp->put_Completed(Callback>(writeCompletedLambda).Get()); Q_ASSERT_SUCCEEDED(hr); return S_OK; }); Q_ASSERT_SUCCEEDED(hr); } void QLowEnergyControllerPrivateWinRT::writeDescriptor( const QSharedPointer service, const QLowEnergyHandle charHandle, const QLowEnergyHandle descHandle, const QByteArray &newValue) { qCDebug(QT_BT_WINRT) << __FUNCTION__ << service << charHandle << descHandle << newValue; 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); Q_ASSERT_SUCCEEDED(hr); auto writeCompletedLambda = [charHandle, descHandle, newValue, service, this] (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); if (FAILED(hr)) { qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle; service->setError(QLowEnergyService::DescriptorWriteError); return S_OK; } if (result != GattCommunicationStatus_Success) { qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed"; service->setError(QLowEnergyService::DescriptorWriteError); return S_OK; } updateValueOfDescriptor(charHandle, descHandle, newValue, false); emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle), newValue); return S_OK; }; hr = writeOp->put_Completed(Callback>(writeCompletedLambda).Get()); Q_ASSERT_SUCCEEDED(hr); } else { ComPtr> descriptors; HRESULT hr = characteristic->GetDescriptors(descData.uuid, &descriptors); Q_ASSERT_SUCCEEDED(hr); ComPtr descriptor; hr = descriptors->GetAt(0, &descriptor); Q_ASSERT_SUCCEEDED(hr); ComPtr bufferFactory; hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(), &bufferFactory); Q_ASSERT_SUCCEEDED(hr); ComPtr buffer; const int length = newValue.length(); hr = bufferFactory->Create(length, &buffer); Q_ASSERT_SUCCEEDED(hr); hr = buffer->put_Length(length); Q_ASSERT_SUCCEEDED(hr); ComPtr byteAccess; hr = buffer.As(&byteAccess); Q_ASSERT_SUCCEEDED(hr); byte *bytes; hr = byteAccess->Buffer(&bytes); Q_ASSERT_SUCCEEDED(hr); memcpy(bytes, newValue, length); ComPtr> writeOp; hr = descriptor->WriteValueAsync(buffer.Get(), &writeOp); Q_ASSERT_SUCCEEDED(hr); auto writeCompletedLambda = [charHandle, descHandle, newValue, service, this] (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); if (FAILED(hr)) { qCDebug(QT_BT_WINRT) << "Could not obtain result for descriptor" << descHandle; service->setError(QLowEnergyService::DescriptorWriteError); return S_OK; } if (result != GattCommunicationStatus_Success) { qCDebug(QT_BT_WINRT) << "Descriptor" << descHandle << "write operation failed"; service->setError(QLowEnergyService::DescriptorWriteError); return S_OK; } updateValueOfDescriptor(charHandle, descHandle, newValue, false); emit service->descriptorWritten(QLowEnergyDescriptor(service, charHandle, descHandle), newValue); return S_OK; }; hr = writeOp->put_Completed(Callback>(writeCompletedLambda).Get()); Q_ASSERT_SUCCEEDED(hr); return S_OK; } return S_OK; }); Q_ASSERT_SUCCEEDED(hr); } void QLowEnergyControllerPrivateWinRT::addToGenericAttributeList(const QLowEnergyServiceData &, QLowEnergyHandle) { Q_UNIMPLEMENTED(); } void QLowEnergyControllerPrivateWinRT::characteristicChanged( int charHandle, const QByteArray &data) { 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); } QT_END_NAMESPACE #include "qlowenergycontroller_winrt.moc"