| /**************************************************************************** |
| ** |
| ** Copyright (C) 2018 The Qt Company Ltd. |
| ** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.com> |
| ** 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_win_p.h" |
| #include "qbluetoothdevicediscoveryagent_p.h" |
| |
| #include <QtCore/QLoggingCategory> |
| #include <QtCore/QIODevice> // for open modes |
| #include <QtCore/QEvent> |
| #include <QtCore/QMutex> |
| #include <QtCore/QThread> |
| #include <QtCore/QDataStream> |
| #include <QtCore/QCoreApplication> |
| |
| #include <algorithm> // for std::max |
| |
| #include <SetupAPI.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS) |
| |
| Q_GLOBAL_STATIC(QLibrary, bluetoothapis) |
| |
| Q_GLOBAL_STATIC(QVector<QLowEnergyControllerPrivateWin32 *>, qControllers) |
| static QMutex controllersGuard(QMutex::NonRecursive); |
| |
| const QEvent::Type CharactericticValueEventType = static_cast<QEvent::Type>(QEvent::User + 1); |
| |
| class CharactericticValueEvent : public QEvent |
| { |
| public: |
| explicit CharactericticValueEvent(const PBLUETOOTH_GATT_VALUE_CHANGED_EVENT gattValueChangedEvent) |
| : QEvent(CharactericticValueEventType) |
| , m_handle(0) |
| { |
| if (!gattValueChangedEvent || gattValueChangedEvent->CharacteristicValueDataSize == 0) |
| return; |
| |
| m_handle = gattValueChangedEvent->ChangedAttributeHandle; |
| |
| const PBTH_LE_GATT_CHARACTERISTIC_VALUE gattValue = gattValueChangedEvent->CharacteristicValue; |
| if (!gattValue) |
| return; |
| |
| m_value = QByteArray(reinterpret_cast<const char *>(&gattValue->Data[0]), |
| int(gattValue->DataSize)); |
| } |
| |
| QByteArray m_value; |
| QLowEnergyHandle m_handle; |
| }; |
| |
| // Bit masks of ClientCharacteristicConfiguration value, see btle spec. |
| namespace ClientCharacteristicConfigurationValue { |
| enum { UseNotifications = 0x1, UseIndications = 0x2 }; |
| } |
| |
| static bool gattFunctionsResolved = false; |
| |
| static QBluetoothAddress getDeviceAddress(const QString &servicePath) |
| { |
| const int firstbound = servicePath.lastIndexOf(QStringLiteral("_")); |
| const int lastbound = servicePath.indexOf(QLatin1Char('#'), firstbound); |
| const QString hex = servicePath.mid(firstbound + 1, lastbound - firstbound - 1); |
| bool ok = false; |
| return QBluetoothAddress(hex.toULongLong(&ok, 16)); |
| } |
| |
| static QString getServiceSystemPath(const QBluetoothAddress &deviceAddress, |
| const QBluetoothUuid &serviceUuid, int *systemErrorCode) |
| { |
| const HDEVINFO deviceInfoSet = ::SetupDiGetClassDevs( |
| reinterpret_cast<const GUID *>(&serviceUuid), |
| nullptr, |
| nullptr, |
| DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); |
| |
| if (deviceInfoSet == INVALID_HANDLE_VALUE) { |
| *systemErrorCode = int(::GetLastError()); |
| return QString(); |
| } |
| |
| QString foundSystemPath; |
| DWORD index = 0; |
| |
| for (;;) { |
| SP_DEVICE_INTERFACE_DATA deviceInterfaceData; |
| ::ZeroMemory(&deviceInterfaceData, sizeof(deviceInterfaceData)); |
| deviceInterfaceData.cbSize = sizeof(deviceInterfaceData); |
| |
| if (!::SetupDiEnumDeviceInterfaces( |
| deviceInfoSet, |
| nullptr, |
| reinterpret_cast<const GUID *>(&serviceUuid), |
| index++, |
| &deviceInterfaceData)) { |
| *systemErrorCode = int(::GetLastError()); |
| break; |
| } |
| |
| DWORD deviceInterfaceDetailDataSize = 0; |
| if (!::SetupDiGetDeviceInterfaceDetail( |
| deviceInfoSet, |
| &deviceInterfaceData, |
| nullptr, |
| deviceInterfaceDetailDataSize, |
| &deviceInterfaceDetailDataSize, |
| nullptr)) { |
| const int error = int(::GetLastError()); |
| if (error != ERROR_INSUFFICIENT_BUFFER) { |
| *systemErrorCode = error; |
| break; |
| } |
| } |
| |
| SP_DEVINFO_DATA deviceInfoData; |
| ::ZeroMemory(&deviceInfoData, sizeof(deviceInfoData)); |
| deviceInfoData.cbSize = sizeof(deviceInfoData); |
| |
| QByteArray deviceInterfaceDetailDataBuffer( |
| int(deviceInterfaceDetailDataSize), 0); |
| |
| PSP_INTERFACE_DEVICE_DETAIL_DATA deviceInterfaceDetailData = |
| reinterpret_cast<PSP_INTERFACE_DEVICE_DETAIL_DATA> |
| (deviceInterfaceDetailDataBuffer.data()); |
| |
| deviceInterfaceDetailData->cbSize = |
| sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA); |
| |
| if (!::SetupDiGetDeviceInterfaceDetail( |
| deviceInfoSet, |
| &deviceInterfaceData, |
| deviceInterfaceDetailData, |
| DWORD(deviceInterfaceDetailDataBuffer.size()), |
| &deviceInterfaceDetailDataSize, |
| &deviceInfoData)) { |
| *systemErrorCode = int(::GetLastError()); |
| break; |
| } |
| |
| // We need to check on required device address which contains in a |
| // system path. As it is not enough to use only service UUID for this. |
| const auto candidateSystemPath = QString::fromWCharArray(deviceInterfaceDetailData->DevicePath); |
| const auto candidateDeviceAddress = getDeviceAddress(candidateSystemPath); |
| if (candidateDeviceAddress == deviceAddress) { |
| foundSystemPath = candidateSystemPath; |
| *systemErrorCode = NO_ERROR; |
| break; |
| } |
| } |
| |
| ::SetupDiDestroyDeviceInfoList(deviceInfoSet); |
| return foundSystemPath; |
| } |
| |
| static HANDLE openSystemDevice( |
| const QString &systemPath, QIODevice::OpenMode openMode, int *systemErrorCode) |
| { |
| DWORD desiredAccess = 0; |
| DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; |
| |
| if (openMode & QIODevice::ReadOnly) { |
| desiredAccess |= GENERIC_READ; |
| } |
| |
| if (openMode & QIODevice::WriteOnly) { |
| desiredAccess |= GENERIC_WRITE; |
| shareMode &= ~DWORD(FILE_SHARE_WRITE); |
| } |
| |
| const HANDLE hDevice = ::CreateFile( |
| reinterpret_cast<const wchar_t *>(systemPath.utf16()), |
| desiredAccess, |
| shareMode, |
| nullptr, |
| OPEN_EXISTING, |
| 0, |
| nullptr); |
| |
| *systemErrorCode = (INVALID_HANDLE_VALUE == hDevice) |
| ? int(::GetLastError()) : NO_ERROR; |
| return hDevice; |
| } |
| |
| static HANDLE openSystemService(const QBluetoothAddress &deviceAddress, |
| const QBluetoothUuid &service, QIODevice::OpenMode openMode, int *systemErrorCode) |
| { |
| const QString serviceSystemPath = getServiceSystemPath( |
| deviceAddress, service, systemErrorCode); |
| |
| if (*systemErrorCode != NO_ERROR) |
| return INVALID_HANDLE_VALUE; |
| |
| const HANDLE hService = openSystemDevice( |
| serviceSystemPath, openMode, systemErrorCode); |
| |
| if (*systemErrorCode != NO_ERROR) |
| return INVALID_HANDLE_VALUE; |
| |
| return hService; |
| } |
| |
| static void closeSystemDevice(HANDLE hDevice) |
| { |
| if (hDevice && hDevice != INVALID_HANDLE_VALUE) |
| ::CloseHandle(hDevice); |
| } |
| |
| static QVector<BTH_LE_GATT_SERVICE> enumeratePrimaryGattServices( |
| HANDLE hDevice, int *systemErrorCode) |
| { |
| if (!gattFunctionsResolved) { |
| *systemErrorCode = ERROR_NOT_SUPPORTED; |
| return QVector<BTH_LE_GATT_SERVICE>(); |
| } |
| |
| QVector<BTH_LE_GATT_SERVICE> foundServices; |
| USHORT servicesCount = 0; |
| for (;;) { |
| const HRESULT hr = ::BluetoothGATTGetServices( |
| hDevice, |
| servicesCount, |
| foundServices.isEmpty() ? nullptr : &foundServices[0], |
| &servicesCount, |
| BLUETOOTH_GATT_FLAG_NONE); |
| |
| if (SUCCEEDED(hr)) { |
| *systemErrorCode = NO_ERROR; |
| return foundServices; |
| } else { |
| const int error = WIN32_FROM_HRESULT(hr); |
| if (error == ERROR_MORE_DATA) { |
| foundServices.resize(servicesCount); |
| } else { |
| *systemErrorCode = error; |
| return QVector<BTH_LE_GATT_SERVICE>(); |
| } |
| } |
| } |
| } |
| |
| static QVector<BTH_LE_GATT_CHARACTERISTIC> enumerateGattCharacteristics( |
| HANDLE hService, PBTH_LE_GATT_SERVICE gattService, int *systemErrorCode) |
| { |
| if (!gattFunctionsResolved) { |
| *systemErrorCode = ERROR_NOT_SUPPORTED; |
| return QVector<BTH_LE_GATT_CHARACTERISTIC>(); |
| } |
| |
| QVector<BTH_LE_GATT_CHARACTERISTIC> foundCharacteristics; |
| USHORT characteristicsCount = 0; |
| for (;;) { |
| const HRESULT hr = ::BluetoothGATTGetCharacteristics( |
| hService, |
| gattService, |
| characteristicsCount, |
| foundCharacteristics.isEmpty() ? nullptr : &foundCharacteristics[0], |
| &characteristicsCount, |
| BLUETOOTH_GATT_FLAG_NONE); |
| |
| if (SUCCEEDED(hr)) { |
| *systemErrorCode = NO_ERROR; |
| return foundCharacteristics; |
| } else { |
| const int error = WIN32_FROM_HRESULT(hr); |
| if (error == ERROR_MORE_DATA) { |
| foundCharacteristics.resize(characteristicsCount); |
| } else { |
| *systemErrorCode = error; |
| return QVector<BTH_LE_GATT_CHARACTERISTIC>(); |
| } |
| } |
| } |
| } |
| |
| static QByteArray getGattCharacteristicValue( |
| HANDLE hService, PBTH_LE_GATT_CHARACTERISTIC gattCharacteristic, int *systemErrorCode) |
| { |
| if (!gattFunctionsResolved) { |
| *systemErrorCode = ERROR_NOT_SUPPORTED; |
| return QByteArray(); |
| } |
| |
| QByteArray valueBuffer; |
| USHORT valueBufferSize = 0; |
| for (;;) { |
| const auto valuePtr = valueBuffer.isEmpty() |
| ? nullptr |
| : reinterpret_cast<PBTH_LE_GATT_CHARACTERISTIC_VALUE>(valueBuffer.data()); |
| |
| const HRESULT hr = ::BluetoothGATTGetCharacteristicValue( |
| hService, |
| gattCharacteristic, |
| valueBufferSize, |
| valuePtr, |
| &valueBufferSize, |
| BLUETOOTH_GATT_FLAG_NONE); |
| |
| if (SUCCEEDED(hr)) { |
| *systemErrorCode = NO_ERROR; |
| return QByteArray(reinterpret_cast<const char *>(&valuePtr->Data[0]), |
| int(valuePtr->DataSize)); |
| } else { |
| const int error = WIN32_FROM_HRESULT(hr); |
| if (error == ERROR_MORE_DATA) { |
| valueBuffer.resize(valueBufferSize); |
| valueBuffer.fill(0); |
| } else { |
| *systemErrorCode = error; |
| return QByteArray(); |
| } |
| } |
| } |
| } |
| |
| static void setGattCharacteristicValue( |
| HANDLE hService, PBTH_LE_GATT_CHARACTERISTIC gattCharacteristic, |
| const QByteArray &value, DWORD flags, int *systemErrorCode) |
| { |
| if (!gattFunctionsResolved) { |
| *systemErrorCode = ERROR_NOT_SUPPORTED; |
| return; |
| } |
| |
| QByteArray valueBuffer; |
| QDataStream out(&valueBuffer, QIODevice::WriteOnly); |
| ULONG dataSize = ULONG(value.size()); |
| out.writeRawData(reinterpret_cast<const char *>(&dataSize), sizeof(dataSize)); |
| out.writeRawData(value.constData(), value.size()); |
| |
| BTH_LE_GATT_RELIABLE_WRITE_CONTEXT reliableWriteContext = 0; |
| |
| const HRESULT hr = ::BluetoothGATTSetCharacteristicValue( |
| hService, |
| gattCharacteristic, |
| reinterpret_cast<PBTH_LE_GATT_CHARACTERISTIC_VALUE>(valueBuffer.data()), |
| reliableWriteContext, |
| flags); |
| |
| if (SUCCEEDED(hr)) |
| *systemErrorCode = NO_ERROR; |
| else |
| *systemErrorCode = WIN32_FROM_HRESULT(hr); |
| } |
| |
| static QVector<BTH_LE_GATT_DESCRIPTOR> enumerateGattDescriptors( |
| HANDLE hService, PBTH_LE_GATT_CHARACTERISTIC gattCharacteristic, int *systemErrorCode) |
| { |
| if (!gattFunctionsResolved) { |
| *systemErrorCode = ERROR_NOT_SUPPORTED; |
| return QVector<BTH_LE_GATT_DESCRIPTOR>(); |
| } |
| |
| QVector<BTH_LE_GATT_DESCRIPTOR> foundDescriptors; |
| USHORT descriptorsCount = 0; |
| for (;;) { |
| const HRESULT hr = ::BluetoothGATTGetDescriptors( |
| hService, |
| gattCharacteristic, |
| descriptorsCount, |
| foundDescriptors.isEmpty() ? nullptr : &foundDescriptors[0], |
| &descriptorsCount, |
| BLUETOOTH_GATT_FLAG_NONE); |
| |
| if (SUCCEEDED(hr)) { |
| *systemErrorCode = NO_ERROR; |
| return foundDescriptors; |
| } else { |
| const int error = WIN32_FROM_HRESULT(hr); |
| if (error == ERROR_MORE_DATA) { |
| foundDescriptors.resize(descriptorsCount); |
| } else { |
| *systemErrorCode = error; |
| return QVector<BTH_LE_GATT_DESCRIPTOR>(); |
| } |
| } |
| } |
| } |
| |
| static QByteArray getGattDescriptorValue( |
| HANDLE hService, PBTH_LE_GATT_DESCRIPTOR gattDescriptor, int *systemErrorCode) |
| { |
| if (!gattFunctionsResolved) { |
| *systemErrorCode = ERROR_NOT_SUPPORTED; |
| return QByteArray(); |
| } |
| |
| QByteArray valueBuffer; |
| USHORT valueBufferSize = 0; |
| for (;;) { |
| const auto valuePtr = valueBuffer.isEmpty() |
| ? nullptr |
| : reinterpret_cast<PBTH_LE_GATT_DESCRIPTOR_VALUE>(valueBuffer.data()); |
| |
| const HRESULT hr = ::BluetoothGATTGetDescriptorValue( |
| hService, |
| gattDescriptor, |
| valueBufferSize, |
| valuePtr, |
| &valueBufferSize, |
| BLUETOOTH_GATT_FLAG_NONE); |
| |
| if (SUCCEEDED(hr)) { |
| *systemErrorCode = NO_ERROR; |
| if (gattDescriptor->DescriptorType == CharacteristicUserDescription) { |
| QString valueString = QString::fromUtf16(reinterpret_cast<const ushort *>(&valuePtr->Data[0]), |
| valuePtr->DataSize/2); |
| return valueString.toUtf8(); |
| } |
| return QByteArray(reinterpret_cast<const char *>(&valuePtr->Data[0]), |
| int(valuePtr->DataSize)); |
| } else { |
| const int error = WIN32_FROM_HRESULT(hr); |
| if (error == ERROR_MORE_DATA) { |
| valueBuffer.resize(valueBufferSize); |
| valueBuffer.fill(0); |
| } else { |
| *systemErrorCode = error; |
| return QByteArray(); |
| } |
| } |
| } |
| } |
| |
| static void setGattDescriptorValue( |
| HANDLE hService, PBTH_LE_GATT_DESCRIPTOR gattDescriptor, |
| QByteArray value, int *systemErrorCode) |
| { |
| if (!gattFunctionsResolved) { |
| *systemErrorCode = ERROR_NOT_SUPPORTED; |
| return; |
| } |
| |
| const int requiredValueBufferSize = int(sizeof(BTH_LE_GATT_DESCRIPTOR_VALUE)) |
| + value.size(); |
| |
| QByteArray valueBuffer(requiredValueBufferSize, 0); |
| |
| PBTH_LE_GATT_DESCRIPTOR_VALUE gattValue = reinterpret_cast< |
| PBTH_LE_GATT_DESCRIPTOR_VALUE>(valueBuffer.data()); |
| |
| gattValue->DescriptorType = gattDescriptor->DescriptorType; |
| |
| if (gattValue->DescriptorType == ClientCharacteristicConfiguration) { |
| QDataStream in(value); |
| quint8 u; |
| in >> u; |
| |
| // We need to setup appropriate fields that allow to subscribe for events. |
| gattValue->ClientCharacteristicConfiguration.IsSubscribeToNotification = |
| bool(u & ClientCharacteristicConfigurationValue::UseNotifications); |
| gattValue->ClientCharacteristicConfiguration.IsSubscribeToIndication = |
| bool(u & ClientCharacteristicConfigurationValue::UseIndications); |
| } |
| |
| gattValue->DataSize = ULONG(value.size()); |
| ::memcpy(gattValue->Data, value.constData(), size_t(value.size())); |
| |
| const HRESULT hr = ::BluetoothGATTSetDescriptorValue( |
| hService, |
| gattDescriptor, |
| gattValue, |
| BLUETOOTH_GATT_FLAG_NONE); |
| |
| if (SUCCEEDED(hr)) |
| *systemErrorCode = NO_ERROR; |
| else |
| *systemErrorCode = WIN32_FROM_HRESULT(hr); |
| } |
| |
| static void WINAPI eventChangedCallbackEntry( |
| BTH_LE_GATT_EVENT_TYPE eventType, PVOID eventOutParameter, PVOID context) |
| { |
| if ((eventType != CharacteristicValueChangedEvent) || !eventOutParameter || !context) |
| return; |
| |
| QMutexLocker locker(&controllersGuard); |
| const auto target = static_cast<QLowEnergyControllerPrivateWin32 *>(context); |
| if (!qControllers->contains(target)) |
| return; |
| |
| CharactericticValueEvent *e = new CharactericticValueEvent( |
| reinterpret_cast<const PBLUETOOTH_GATT_VALUE_CHANGED_EVENT>(eventOutParameter)); |
| |
| QCoreApplication::postEvent(target, e); |
| } |
| |
| static HANDLE registerEvent( |
| HANDLE hService, BTH_LE_GATT_CHARACTERISTIC gattCharacteristic, |
| PVOID context, int *systemErrorCode) |
| { |
| if (!gattFunctionsResolved) { |
| *systemErrorCode = ERROR_NOT_SUPPORTED; |
| return INVALID_HANDLE_VALUE; |
| } |
| |
| HANDLE hEvent = INVALID_HANDLE_VALUE; |
| |
| BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION registration; |
| ::ZeroMemory(®istration, sizeof(registration)); |
| registration.NumCharacteristics = 1; |
| registration.Characteristics[0] = gattCharacteristic; |
| |
| const HRESULT hr = ::BluetoothGATTRegisterEvent( |
| hService, |
| CharacteristicValueChangedEvent, |
| ®istration, |
| eventChangedCallbackEntry, |
| context, |
| &hEvent, |
| BLUETOOTH_GATT_FLAG_NONE); |
| |
| if (SUCCEEDED(hr)) |
| *systemErrorCode = NO_ERROR; |
| else |
| *systemErrorCode = WIN32_FROM_HRESULT(hr); |
| |
| return hEvent; |
| } |
| |
| static void unregisterEvent(HANDLE hEvent, int *systemErrorCode) |
| { |
| if (!gattFunctionsResolved) { |
| *systemErrorCode = ERROR_NOT_SUPPORTED; |
| return; |
| } |
| |
| const HRESULT hr = ::BluetoothGATTUnregisterEvent( |
| hEvent, |
| BLUETOOTH_GATT_FLAG_NONE); |
| |
| if (SUCCEEDED(hr)) |
| *systemErrorCode = NO_ERROR; |
| else |
| *systemErrorCode = WIN32_FROM_HRESULT(hr); |
| } |
| |
| static QBluetoothUuid qtBluetoothUuidFromNativeLeUuid(const BTH_LE_UUID &uuid) |
| { |
| return uuid.IsShortUuid ? QBluetoothUuid(uuid.Value.ShortUuid) |
| : QBluetoothUuid(uuid.Value.LongUuid); |
| } |
| |
| static BTH_LE_UUID nativeLeUuidFromQtBluetoothUuid(const QBluetoothUuid &uuid) |
| { |
| BTH_LE_UUID gattUuid; |
| ::ZeroMemory(&gattUuid, sizeof(gattUuid)); |
| if (uuid.minimumSize() == 2) { |
| gattUuid.IsShortUuid = TRUE; |
| gattUuid.Value.ShortUuid = USHORT(uuid.data1); // other fields should be empty! |
| } else { |
| gattUuid.Value.LongUuid = uuid; |
| } |
| return gattUuid; |
| } |
| |
| static BTH_LE_GATT_CHARACTERISTIC recoverNativeLeGattCharacteristic( |
| QLowEnergyHandle serviceHandle, QLowEnergyHandle characteristicHandle, |
| const QLowEnergyServicePrivate::CharData &characteristicData) |
| { |
| BTH_LE_GATT_CHARACTERISTIC gattCharacteristic; |
| |
| gattCharacteristic.ServiceHandle = serviceHandle; |
| gattCharacteristic.AttributeHandle = characteristicHandle; |
| gattCharacteristic.CharacteristicValueHandle = characteristicData.valueHandle; |
| |
| gattCharacteristic.CharacteristicUuid = nativeLeUuidFromQtBluetoothUuid( |
| characteristicData.uuid); |
| |
| gattCharacteristic.HasExtendedProperties = bool(characteristicData.properties |
| & QLowEnergyCharacteristic::ExtendedProperty); |
| gattCharacteristic.IsBroadcastable = bool(characteristicData.properties |
| & QLowEnergyCharacteristic::Broadcasting); |
| gattCharacteristic.IsIndicatable = bool(characteristicData.properties |
| & QLowEnergyCharacteristic::Indicate); |
| gattCharacteristic.IsNotifiable = bool(characteristicData.properties |
| & QLowEnergyCharacteristic::Notify); |
| gattCharacteristic.IsReadable = bool(characteristicData.properties |
| & QLowEnergyCharacteristic::Read); |
| gattCharacteristic.IsSignedWritable = bool(characteristicData.properties |
| & QLowEnergyCharacteristic::WriteSigned); |
| gattCharacteristic.IsWritable = bool(characteristicData.properties |
| & QLowEnergyCharacteristic::Write); |
| gattCharacteristic.IsWritableWithoutResponse = bool(characteristicData.properties |
| & QLowEnergyCharacteristic::WriteNoResponse); |
| |
| return gattCharacteristic; |
| } |
| |
| static BTH_LE_GATT_DESCRIPTOR_TYPE nativeLeGattDescriptorTypeFromUuid( |
| const QBluetoothUuid &uuid) |
| { |
| switch (uuid.toUInt16()) { |
| case QBluetoothUuid::CharacteristicExtendedProperties: |
| return CharacteristicExtendedProperties; |
| case QBluetoothUuid::CharacteristicUserDescription: |
| return CharacteristicUserDescription; |
| case QBluetoothUuid::ClientCharacteristicConfiguration: |
| return ClientCharacteristicConfiguration; |
| case QBluetoothUuid::ServerCharacteristicConfiguration: |
| return ServerCharacteristicConfiguration; |
| case QBluetoothUuid::CharacteristicPresentationFormat: |
| return CharacteristicFormat; |
| case QBluetoothUuid::CharacteristicAggregateFormat: |
| return CharacteristicAggregateFormat; |
| default: |
| return CustomDescriptor; |
| } |
| } |
| |
| static BTH_LE_GATT_DESCRIPTOR recoverNativeLeGattDescriptor( |
| QLowEnergyHandle serviceHandle, QLowEnergyHandle characteristicHandle, |
| QLowEnergyHandle descriptorHandle, |
| const QLowEnergyServicePrivate::DescData &descriptorData) |
| { |
| BTH_LE_GATT_DESCRIPTOR gattDescriptor; |
| |
| gattDescriptor.ServiceHandle = serviceHandle; |
| gattDescriptor.CharacteristicHandle = characteristicHandle; |
| gattDescriptor.AttributeHandle = descriptorHandle; |
| |
| gattDescriptor.DescriptorUuid = nativeLeUuidFromQtBluetoothUuid( |
| descriptorData.uuid); |
| |
| gattDescriptor.DescriptorType = nativeLeGattDescriptorTypeFromUuid |
| (descriptorData.uuid); |
| |
| return gattDescriptor; |
| } |
| |
| void QLowEnergyControllerPrivateWin32::customEvent(QEvent *e) |
| { |
| if (e->type() != CharactericticValueEventType) |
| return; |
| |
| const CharactericticValueEvent *characteristicEvent |
| = static_cast<CharactericticValueEvent *>(e); |
| |
| updateValueOfCharacteristic(characteristicEvent->m_handle, |
| characteristicEvent->m_value, false); |
| |
| const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle( |
| characteristicEvent->m_handle); |
| if (service.isNull()) |
| return; |
| |
| const QLowEnergyCharacteristic ch(service, characteristicEvent->m_handle); |
| emit service->characteristicChanged(ch, characteristicEvent->m_value); |
| } |
| |
| QLowEnergyControllerPrivateWin32::QLowEnergyControllerPrivateWin32() |
| : QLowEnergyControllerPrivate() |
| { |
| QMutexLocker locker(&controllersGuard); |
| qControllers()->append(this); |
| |
| gattFunctionsResolved = resolveFunctions(bluetoothapis()); |
| if (!gattFunctionsResolved) { |
| qCWarning(QT_BT_WINDOWS) << "LE is not supported on this OS"; |
| return; |
| } |
| } |
| |
| QLowEnergyControllerPrivateWin32::~QLowEnergyControllerPrivateWin32() |
| { |
| QMutexLocker locker(&controllersGuard); |
| qControllers()->removeAll(this); |
| } |
| |
| void QLowEnergyControllerPrivateWin32::init() |
| { |
| } |
| |
| void QLowEnergyControllerPrivateWin32::connectToDevice() |
| { |
| // required to pass unit test on default backend |
| if (remoteDevice.isNull()) { |
| qWarning() << "Invalid/null remote device address"; |
| setError(QLowEnergyController::UnknownRemoteDeviceError); |
| return; |
| } |
| |
| if (!deviceSystemPath.isEmpty()) { |
| qCDebug(QT_BT_WINDOWS) << "Already is connected"; |
| return; |
| } |
| |
| setState(QLowEnergyController::ConnectingState); |
| |
| deviceSystemPath = |
| QBluetoothDeviceDiscoveryAgentPrivate::discoveredLeDeviceSystemPath( |
| remoteDevice); |
| |
| if (deviceSystemPath.isEmpty()) { |
| qCWarning(QT_BT_WINDOWS) << qt_error_string(ERROR_PATH_NOT_FOUND); |
| setError(QLowEnergyController::UnknownRemoteDeviceError); |
| setState(QLowEnergyController::UnconnectedState); |
| return; |
| } |
| |
| setState(QLowEnergyController::ConnectedState); |
| |
| thread = new QThread; |
| threadWorker = new ThreadWorker; |
| threadWorker->moveToThread(thread); |
| connect(threadWorker, &ThreadWorker::jobFinished, this, &QLowEnergyControllerPrivateWin32::jobFinished); |
| connect(thread, &QThread::finished, threadWorker, &ThreadWorker::deleteLater); |
| connect(thread, &QThread::finished, thread, &QThread::deleteLater); |
| thread->start(); |
| |
| Q_Q(QLowEnergyController); |
| emit q->connected(); |
| } |
| |
| void QLowEnergyControllerPrivateWin32::disconnectFromDevice() |
| { |
| if (deviceSystemPath.isEmpty()) { |
| qCDebug(QT_BT_WINDOWS) << "Already is disconnected"; |
| return; |
| } |
| |
| setState(QLowEnergyController::ClosingState); |
| deviceSystemPath.clear(); |
| setState(QLowEnergyController::UnconnectedState); |
| |
| if (thread) { |
| disconnect(threadWorker, &ThreadWorker::jobFinished, this, &QLowEnergyControllerPrivateWin32::jobFinished); |
| thread->quit(); |
| thread = nullptr; |
| } |
| |
| for (const auto &servicePrivate: serviceList) |
| closeSystemDevice(servicePrivate->hService); |
| |
| Q_Q(QLowEnergyController); |
| emit q->disconnected(); |
| } |
| |
| void QLowEnergyControllerPrivateWin32::discoverServices() |
| { |
| int systemErrorCode = NO_ERROR; |
| |
| const HANDLE hDevice = openSystemDevice( |
| deviceSystemPath, QIODevice::ReadOnly, &systemErrorCode); |
| |
| if (systemErrorCode != NO_ERROR) { |
| qCWarning(QT_BT_WINDOWS) << qt_error_string(systemErrorCode); |
| setError(QLowEnergyController::NetworkError); |
| setState(QLowEnergyController::ConnectedState); |
| return; |
| } |
| |
| const QVector<BTH_LE_GATT_SERVICE> foundServices = |
| enumeratePrimaryGattServices(hDevice, &systemErrorCode); |
| |
| closeSystemDevice(hDevice); |
| |
| if (systemErrorCode != NO_ERROR) { |
| qCWarning(QT_BT_WINDOWS) << qt_error_string(systemErrorCode); |
| setError(QLowEnergyController::NetworkError); |
| setState(QLowEnergyController::ConnectedState); |
| return; |
| } |
| |
| setState(QLowEnergyController::DiscoveringState); |
| |
| Q_Q(QLowEnergyController); |
| |
| for (const BTH_LE_GATT_SERVICE &service : foundServices) { |
| const QBluetoothUuid uuid = qtBluetoothUuidFromNativeLeUuid( |
| service.ServiceUuid); |
| qCDebug(QT_BT_WINDOWS) << "Found uuid:" << uuid; |
| |
| QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate(); |
| priv->uuid = uuid; |
| priv->type = QLowEnergyService::PrimaryService; |
| priv->startHandle = service.AttributeHandle; |
| priv->setController(this); |
| |
| QSharedPointer<QLowEnergyServicePrivate> pointer(priv); |
| serviceList.insert(uuid, pointer); |
| |
| emit q->serviceDiscovered(uuid); |
| } |
| |
| setState(QLowEnergyController::DiscoveredState); |
| emit q->discoveryFinished(); |
| } |
| |
| void QLowEnergyControllerPrivateWin32::discoverServiceDetails( |
| const QBluetoothUuid &service) |
| { |
| if (!serviceList.contains(service)) { |
| qCWarning(QT_BT_WINDOWS) << "Discovery of unknown service" << service.toString() |
| << "not possible"; |
| return; |
| } |
| |
| const QSharedPointer<QLowEnergyServicePrivate> servicePrivate = |
| serviceList.value(service); |
| |
| int systemErrorCode = NO_ERROR; |
| |
| // Only open a service once and close it in the QLowEnergyServicePrivate destructor |
| if (!servicePrivate->hService || servicePrivate->hService == INVALID_HANDLE_VALUE) { |
| servicePrivate->hService = openSystemService(remoteDevice, service, |
| QIODevice::ReadOnly | QIODevice::WriteOnly, |
| &systemErrorCode); |
| if (systemErrorCode != NO_ERROR) { |
| servicePrivate->hService = openSystemService(remoteDevice, service, |
| QIODevice::ReadOnly, |
| &systemErrorCode); |
| } |
| } |
| |
| if (systemErrorCode != NO_ERROR) { |
| qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service.toString() |
| << ":" << qt_error_string(systemErrorCode); |
| servicePrivate->setError(QLowEnergyService::UnknownError); |
| servicePrivate->setState(QLowEnergyService::DiscoveryRequired); |
| return; |
| } |
| |
| // We assume that the service does not have any characteristics with descriptors. |
| servicePrivate->endHandle = servicePrivate->startHandle; |
| |
| const QVector<BTH_LE_GATT_CHARACTERISTIC> foundCharacteristics = |
| enumerateGattCharacteristics(servicePrivate->hService, nullptr, &systemErrorCode); |
| |
| if (systemErrorCode != NO_ERROR) { |
| qCWarning(QT_BT_WINDOWS) << "Unable to get characteristics for service" << service.toString() |
| << ":" << qt_error_string(systemErrorCode); |
| servicePrivate->setError(QLowEnergyService::CharacteristicReadError); |
| servicePrivate->setState(QLowEnergyService::DiscoveryRequired); |
| return; |
| } |
| |
| for (const BTH_LE_GATT_CHARACTERISTIC &gattCharacteristic : foundCharacteristics) { |
| const QLowEnergyHandle characteristicHandle = gattCharacteristic.AttributeHandle; |
| |
| QLowEnergyServicePrivate::CharData detailsData; |
| |
| detailsData.hValueChangeEvent = nullptr; |
| |
| detailsData.uuid = qtBluetoothUuidFromNativeLeUuid( |
| gattCharacteristic.CharacteristicUuid); |
| detailsData.valueHandle = gattCharacteristic.CharacteristicValueHandle; |
| |
| QLowEnergyCharacteristic::PropertyTypes properties = QLowEnergyCharacteristic::Unknown; |
| if (gattCharacteristic.HasExtendedProperties) |
| properties |= QLowEnergyCharacteristic::ExtendedProperty; |
| if (gattCharacteristic.IsBroadcastable) |
| properties |= QLowEnergyCharacteristic::Broadcasting; |
| if (gattCharacteristic.IsIndicatable) |
| properties |= QLowEnergyCharacteristic::Indicate; |
| if (gattCharacteristic.IsNotifiable) |
| properties |= QLowEnergyCharacteristic::Notify; |
| if (gattCharacteristic.IsReadable) |
| properties |= QLowEnergyCharacteristic::Read; |
| if (gattCharacteristic.IsSignedWritable) |
| properties |= QLowEnergyCharacteristic::WriteSigned; |
| if (gattCharacteristic.IsWritable) |
| properties |= QLowEnergyCharacteristic::Write; |
| if (gattCharacteristic.IsWritableWithoutResponse) |
| properties |= QLowEnergyCharacteristic::WriteNoResponse; |
| |
| detailsData.properties = properties; |
| detailsData.value = getGattCharacteristicValue( |
| servicePrivate->hService, const_cast<PBTH_LE_GATT_CHARACTERISTIC>( |
| &gattCharacteristic), &systemErrorCode); |
| |
| if (systemErrorCode != NO_ERROR) { |
| // We do not interrupt enumerating of characteristics |
| // if value can not be read |
| qCWarning(QT_BT_WINDOWS) << "Unable to get value for characteristic" |
| << detailsData.uuid.toString() |
| << "of the service" << service.toString() |
| << ":" << qt_error_string(systemErrorCode); |
| } |
| |
| // We assume that the characteristic has no any descriptors. So, the |
| // biggest characteristic + 1 will indicate an end handle of service. |
| servicePrivate->endHandle = std::max( |
| servicePrivate->endHandle, |
| QLowEnergyHandle(gattCharacteristic.AttributeHandle + 1)); |
| |
| const QVector<BTH_LE_GATT_DESCRIPTOR> foundDescriptors = enumerateGattDescriptors( |
| servicePrivate->hService, const_cast<PBTH_LE_GATT_CHARACTERISTIC>( |
| &gattCharacteristic), &systemErrorCode); |
| |
| if (systemErrorCode != NO_ERROR) { |
| if (systemErrorCode != ERROR_NOT_FOUND) { |
| qCWarning(QT_BT_WINDOWS) << "Unable to get descriptor for characteristic" |
| << detailsData.uuid.toString() |
| << "of the service" << service.toString() |
| << ":" << qt_error_string(systemErrorCode); |
| servicePrivate->setError(QLowEnergyService::DescriptorReadError); |
| servicePrivate->setState(QLowEnergyService::DiscoveryRequired); |
| return; |
| } |
| } |
| |
| for (const BTH_LE_GATT_DESCRIPTOR &gattDescriptor : foundDescriptors) { |
| const QLowEnergyHandle descriptorHandle = gattDescriptor.AttributeHandle; |
| |
| QLowEnergyServicePrivate::DescData data; |
| data.uuid = qtBluetoothUuidFromNativeLeUuid( |
| gattDescriptor.DescriptorUuid); |
| |
| data.value = getGattDescriptorValue(servicePrivate->hService, const_cast<PBTH_LE_GATT_DESCRIPTOR>( |
| &gattDescriptor), &systemErrorCode); |
| |
| if (systemErrorCode != NO_ERROR) { |
| qCWarning(QT_BT_WINDOWS) << "Unable to get value for descriptor" |
| << data.uuid.toString() |
| << "for characteristic" |
| << detailsData.uuid.toString() |
| << "of the service" << service.toString() |
| << ":" << qt_error_string(systemErrorCode); |
| servicePrivate->setError(QLowEnergyService::DescriptorReadError); |
| servicePrivate->setState(QLowEnergyService::DiscoveryRequired); |
| return; |
| } |
| |
| // Biggest descriptor will contain an end handle of service. |
| servicePrivate->endHandle = std::max( |
| servicePrivate->endHandle, |
| QLowEnergyHandle(gattDescriptor.AttributeHandle)); |
| |
| detailsData.descriptorList.insert(descriptorHandle, data); |
| } |
| |
| servicePrivate->characteristicList.insert(characteristicHandle, detailsData); |
| } |
| |
| servicePrivate->setState(QLowEnergyService::ServiceDiscovered); |
| } |
| |
| void QLowEnergyControllerPrivateWin32::startAdvertising(const QLowEnergyAdvertisingParameters &, const QLowEnergyAdvertisingData &, const QLowEnergyAdvertisingData &) |
| { |
| Q_UNIMPLEMENTED(); |
| } |
| |
| void QLowEnergyControllerPrivateWin32::stopAdvertising() |
| { |
| Q_UNIMPLEMENTED(); |
| } |
| |
| void QLowEnergyControllerPrivateWin32::requestConnectionUpdate(const QLowEnergyConnectionParameters &) |
| { |
| Q_UNIMPLEMENTED(); |
| } |
| |
| void QLowEnergyControllerPrivateWin32::readCharacteristic( |
| const QSharedPointer<QLowEnergyServicePrivate> service, |
| const QLowEnergyHandle charHandle) |
| { |
| Q_ASSERT(!service.isNull()); |
| if (!service->characteristicList.contains(charHandle)) |
| return; |
| |
| const QLowEnergyServicePrivate::CharData &charDetails |
| = service->characteristicList[charHandle]; |
| if (!(charDetails.properties & QLowEnergyCharacteristic::Read)) { |
| // if this succeeds the device has a bug, char is advertised as |
| // non-readable. We try to be permissive and let the remote |
| // device answer to the read attempt |
| qCWarning(QT_BT_WINDOWS) << "Reading non-readable char" << charHandle; |
| } |
| |
| ReadCharData data; |
| data.systemErrorCode = NO_ERROR; |
| data.hService = service->hService; |
| |
| if (data.systemErrorCode != NO_ERROR) { |
| qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString() |
| << ":" << qt_error_string(data.systemErrorCode); |
| service->setError(QLowEnergyService::CharacteristicReadError); |
| return; |
| } |
| |
| data.gattCharacteristic = recoverNativeLeGattCharacteristic( |
| service->startHandle, charHandle, charDetails); |
| |
| ThreadWorkerJob job; |
| job.operation = ThreadWorkerJob::ReadChar; |
| job.data = QVariant::fromValue(data); |
| |
| QMetaObject::invokeMethod(threadWorker, "putJob", Qt::QueuedConnection, |
| Q_ARG(ThreadWorkerJob, job)); |
| } |
| |
| void QLowEnergyControllerPrivateWin32::writeCharacteristic( |
| const QSharedPointer<QLowEnergyServicePrivate> service, |
| const QLowEnergyHandle charHandle, |
| const QByteArray &newValue, |
| QLowEnergyService::WriteMode mode) |
| { |
| Q_ASSERT(!service.isNull()); |
| |
| if (!service->characteristicList.contains(charHandle)) { |
| service->setError(QLowEnergyService::CharacteristicWriteError); |
| return; |
| } |
| |
| WriteCharData data; |
| data.systemErrorCode = NO_ERROR; |
| data.hService = service->hService; |
| |
| if (data.systemErrorCode != NO_ERROR) { |
| qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString() |
| << ":" << qt_error_string(data.systemErrorCode); |
| service->setError(QLowEnergyService::CharacteristicWriteError); |
| return; |
| } |
| |
| const QLowEnergyServicePrivate::CharData &charDetails |
| = service->characteristicList[charHandle]; |
| |
| data.gattCharacteristic = recoverNativeLeGattCharacteristic( |
| service->startHandle, charHandle, charDetails); |
| |
| data.flags = (mode == QLowEnergyService::WriteWithResponse) |
| ? BLUETOOTH_GATT_FLAG_NONE |
| : BLUETOOTH_GATT_FLAG_WRITE_WITHOUT_RESPONSE; |
| |
| ThreadWorkerJob job; |
| job.operation = ThreadWorkerJob::WriteChar; |
| data.newValue = newValue; |
| data.mode = mode; |
| job.data = QVariant::fromValue(data); |
| |
| QMetaObject::invokeMethod(threadWorker, "putJob", Qt::QueuedConnection, |
| Q_ARG(ThreadWorkerJob, job)); |
| } |
| |
| void QLowEnergyControllerPrivateWin32::jobFinished(const ThreadWorkerJob &job) |
| { |
| switch (job.operation) { |
| case ThreadWorkerJob::WriteChar: |
| { |
| const WriteCharData data = job.data.value<WriteCharData>(); |
| const QLowEnergyHandle charHandle = static_cast<QLowEnergyHandle>(data.gattCharacteristic.AttributeHandle); |
| const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); |
| |
| if (data.systemErrorCode != NO_ERROR) { |
| const QLowEnergyServicePrivate::CharData &charDetails = service->characteristicList[charHandle]; |
| qCWarning(QT_BT_WINDOWS) << "Unable to set value for characteristic" |
| << charDetails.uuid.toString() |
| << "of the service" << service->uuid.toString() |
| << ":" << qt_error_string(data.systemErrorCode); |
| service->setError(QLowEnergyService::CharacteristicWriteError); |
| return; |
| } |
| |
| updateValueOfCharacteristic(charHandle, data.newValue, false); |
| |
| if (data.mode == QLowEnergyService::WriteWithResponse) { |
| const QLowEnergyCharacteristic ch = characteristicForHandle(charHandle); |
| emit service->characteristicWritten(ch, data.newValue); |
| } |
| } |
| break; |
| case ThreadWorkerJob::ReadChar: |
| { |
| const ReadCharData data = job.data.value<ReadCharData>(); |
| const QLowEnergyHandle charHandle = static_cast<QLowEnergyHandle>(data.gattCharacteristic.AttributeHandle); |
| const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); |
| |
| if (data.systemErrorCode != NO_ERROR) { |
| const QLowEnergyServicePrivate::CharData &charDetails = service->characteristicList[charHandle]; |
| qCWarning(QT_BT_WINDOWS) << "Unable to get value for characteristic" |
| << charDetails.uuid.toString() |
| << "of the service" << service->uuid.toString() |
| << ":" << qt_error_string(data.systemErrorCode); |
| service->setError(QLowEnergyService::CharacteristicReadError); |
| return; |
| } |
| |
| updateValueOfCharacteristic(charHandle, data.value, false); |
| |
| const QLowEnergyCharacteristic ch(service, charHandle); |
| emit service->characteristicRead(ch, data.value); |
| } |
| break; |
| case ThreadWorkerJob::WriteDescr: |
| { |
| WriteDescData data = job.data.value<WriteDescData>(); |
| const QLowEnergyHandle descriptorHandle = static_cast<QLowEnergyHandle>(data.gattDescriptor.AttributeHandle); |
| const QLowEnergyHandle charHandle = static_cast<QLowEnergyHandle>(data.gattDescriptor.CharacteristicHandle); |
| const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); |
| QLowEnergyServicePrivate::CharData &charDetails = service->characteristicList[charHandle]; |
| const QLowEnergyServicePrivate::DescData &dscrDetails = charDetails.descriptorList[descriptorHandle]; |
| |
| if (data.systemErrorCode != NO_ERROR) { |
| qCWarning(QT_BT_WINDOWS) << "Unable to set value for descriptor" |
| << dscrDetails.uuid.toString() |
| << "for characteristic" |
| << charDetails.uuid.toString() |
| << "of the service" << service->uuid.toString() |
| << ":" << qt_error_string(data.systemErrorCode); |
| service->setError(QLowEnergyService::DescriptorWriteError); |
| return; |
| } |
| |
| if (data.gattDescriptor.DescriptorType == ClientCharacteristicConfiguration) { |
| |
| QDataStream in(data.newValue); |
| quint8 u; |
| in >> u; |
| |
| if (u & ClientCharacteristicConfigurationValue::UseNotifications |
| || u & ClientCharacteristicConfigurationValue::UseIndications) { |
| if (!charDetails.hValueChangeEvent) { |
| BTH_LE_GATT_CHARACTERISTIC gattCharacteristic = recoverNativeLeGattCharacteristic( |
| service->startHandle, charHandle, charDetails); |
| |
| // note: if the service handle is closed the event registration is no longer valid. |
| charDetails.hValueChangeEvent = registerEvent( |
| data.hService, gattCharacteristic, this, &data.systemErrorCode); |
| } |
| } else { |
| if (charDetails.hValueChangeEvent) { |
| unregisterEvent(charDetails.hValueChangeEvent, &data.systemErrorCode); |
| charDetails.hValueChangeEvent = nullptr; |
| } |
| } |
| |
| if (data.systemErrorCode != NO_ERROR) { |
| qCWarning(QT_BT_WINDOWS) << "Unable to subscribe events for descriptor" |
| << dscrDetails.uuid.toString() |
| << "for characteristic" |
| << charDetails.uuid.toString() |
| << "of the service" << service->uuid.toString() |
| << ":" << qt_error_string(data.systemErrorCode); |
| service->setError(QLowEnergyService::DescriptorWriteError); |
| return; |
| } |
| } |
| |
| updateValueOfDescriptor(charHandle, descriptorHandle, data.newValue, false); |
| |
| const QLowEnergyDescriptor dscr(service, charHandle, descriptorHandle); |
| emit service->descriptorWritten(dscr, data.newValue); |
| } |
| break; |
| case ThreadWorkerJob::ReadDescr: |
| { |
| ReadDescData data = job.data.value<ReadDescData>(); |
| const QLowEnergyHandle descriptorHandle = static_cast<QLowEnergyHandle>(data.gattDescriptor.AttributeHandle); |
| const QLowEnergyHandle charHandle = static_cast<QLowEnergyHandle>(data.gattDescriptor.CharacteristicHandle); |
| const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); |
| QLowEnergyServicePrivate::CharData &charDetails = service->characteristicList[charHandle]; |
| const QLowEnergyServicePrivate::DescData &dscrDetails = charDetails.descriptorList[descriptorHandle]; |
| |
| if (data.systemErrorCode != NO_ERROR) { |
| qCWarning(QT_BT_WINDOWS) << "Unable to get value for descriptor" |
| << dscrDetails.uuid.toString() |
| << "for characteristic" |
| << charDetails.uuid.toString() |
| << "of the service" << service->uuid.toString() |
| << ":" << qt_error_string(data.systemErrorCode); |
| service->setError(QLowEnergyService::DescriptorReadError); |
| return; |
| } |
| |
| updateValueOfDescriptor(charHandle, descriptorHandle, data.value, false); |
| |
| QLowEnergyDescriptor dscr(service, charHandle, descriptorHandle); |
| emit service->descriptorRead(dscr, data.value); |
| } |
| break; |
| } |
| |
| QMetaObject::invokeMethod(threadWorker, "runPendingJob", Qt::QueuedConnection); |
| } |
| |
| void QLowEnergyControllerPrivateWin32::readDescriptor( |
| const QSharedPointer<QLowEnergyServicePrivate> service, |
| const QLowEnergyHandle charHandle, |
| const QLowEnergyHandle descriptorHandle) |
| { |
| Q_ASSERT(!service.isNull()); |
| if (!service->characteristicList.contains(charHandle)) |
| return; |
| |
| const QLowEnergyServicePrivate::CharData &charDetails |
| = service->characteristicList[charHandle]; |
| if (!charDetails.descriptorList.contains(descriptorHandle)) |
| return; |
| |
| ReadDescData data; |
| data.systemErrorCode = NO_ERROR; |
| data.hService = service->hService; |
| |
| if (data.systemErrorCode != NO_ERROR) { |
| qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString() |
| << ":" << qt_error_string(data.systemErrorCode); |
| service->setError(QLowEnergyService::DescriptorReadError); |
| return; |
| } |
| |
| const QLowEnergyServicePrivate::DescData &dscrDetails |
| = charDetails.descriptorList[descriptorHandle]; |
| |
| data.gattDescriptor = recoverNativeLeGattDescriptor( |
| service->startHandle, charHandle, descriptorHandle, dscrDetails); |
| |
| ThreadWorkerJob job; |
| job.operation = ThreadWorkerJob::ReadDescr; |
| job.data = QVariant::fromValue(data); |
| |
| QMetaObject::invokeMethod(threadWorker, "putJob", Qt::QueuedConnection, |
| Q_ARG(ThreadWorkerJob, job)); |
| } |
| |
| void QLowEnergyControllerPrivateWin32::writeDescriptor( |
| const QSharedPointer<QLowEnergyServicePrivate> service, |
| const QLowEnergyHandle charHandle, |
| const QLowEnergyHandle descriptorHandle, |
| const QByteArray &newValue) |
| { |
| Q_ASSERT(!service.isNull()); |
| if (!service->characteristicList.contains(charHandle)) |
| return; |
| |
| QLowEnergyServicePrivate::CharData &charDetails |
| = service->characteristicList[charHandle]; |
| if (!charDetails.descriptorList.contains(descriptorHandle)) |
| return; |
| |
| WriteDescData data; |
| data.systemErrorCode = NO_ERROR; |
| data.newValue = newValue; |
| data.hService = service->hService; |
| |
| if (data.systemErrorCode != NO_ERROR) { |
| qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString() |
| << ":" << qt_error_string(data.systemErrorCode); |
| service->setError(QLowEnergyService::DescriptorWriteError); |
| return; |
| } |
| |
| const QLowEnergyServicePrivate::DescData &dscrDetails |
| = charDetails.descriptorList[descriptorHandle]; |
| |
| data.gattDescriptor = recoverNativeLeGattDescriptor( |
| service->startHandle, charHandle, descriptorHandle, dscrDetails); |
| |
| ThreadWorkerJob job; |
| job.operation = ThreadWorkerJob::WriteDescr; |
| job.data = QVariant::fromValue(data); |
| |
| QMetaObject::invokeMethod(threadWorker, "putJob", Qt::QueuedConnection, |
| Q_ARG(ThreadWorkerJob, job)); |
| } |
| |
| void QLowEnergyControllerPrivateWin32::addToGenericAttributeList(const QLowEnergyServiceData &, QLowEnergyHandle) |
| { |
| Q_UNIMPLEMENTED(); |
| } |
| |
| void ThreadWorker::putJob(const ThreadWorkerJob &job) |
| { |
| m_jobs.append(job); |
| if (m_jobs.count() == 1) |
| runPendingJob(); |
| } |
| |
| void ThreadWorker::runPendingJob() |
| { |
| if (!m_jobs.count()) |
| return; |
| |
| ThreadWorkerJob job = m_jobs.first(); |
| |
| switch (job.operation) { |
| case ThreadWorkerJob::WriteChar: |
| { |
| WriteCharData data = job.data.value<WriteCharData>(); |
| setGattCharacteristicValue(data.hService, &data.gattCharacteristic, |
| data.newValue, data.flags, &data.systemErrorCode); |
| job.data = QVariant::fromValue(data); |
| } |
| break; |
| case ThreadWorkerJob::ReadChar: |
| { |
| ReadCharData data = job.data.value<ReadCharData>(); |
| data.value = getGattCharacteristicValue( |
| data.hService, &data.gattCharacteristic, &data.systemErrorCode); |
| job.data = QVariant::fromValue(data); |
| } |
| break; |
| case ThreadWorkerJob::WriteDescr: |
| { |
| WriteDescData data = job.data.value<WriteDescData>(); |
| setGattDescriptorValue(data.hService, &data.gattDescriptor, |
| data.newValue, &data.systemErrorCode); |
| job.data = QVariant::fromValue(data); |
| } |
| break; |
| case ThreadWorkerJob::ReadDescr: |
| { |
| ReadDescData data = job.data.value<ReadDescData>(); |
| data.value = getGattDescriptorValue( |
| data.hService, |
| const_cast<PBTH_LE_GATT_DESCRIPTOR>(&data.gattDescriptor), |
| &data.systemErrorCode); |
| job.data = QVariant::fromValue(data); |
| } |
| break; |
| } |
| |
| m_jobs.removeFirst(); |
| emit jobFinished(job); |
| } |
| |
| QT_END_NAMESPACE |