/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Copyright (C) 2014 Denis Shienkov ** 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 #include // for open modes #include #include #include #include #include #include // for std::max #include QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS) Q_GLOBAL_STATIC(QLibrary, bluetoothapis) Q_GLOBAL_STATIC(QVector, qControllers) static QMutex controllersGuard(QMutex::NonRecursive); const QEvent::Type CharactericticValueEventType = static_cast(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(&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(&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(&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 (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(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 enumeratePrimaryGattServices( HANDLE hDevice, int *systemErrorCode) { if (!gattFunctionsResolved) { *systemErrorCode = ERROR_NOT_SUPPORTED; return QVector(); } QVector 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(); } } } } static QVector enumerateGattCharacteristics( HANDLE hService, PBTH_LE_GATT_SERVICE gattService, int *systemErrorCode) { if (!gattFunctionsResolved) { *systemErrorCode = ERROR_NOT_SUPPORTED; return QVector(); } QVector 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(); } } } } 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(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(&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(&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(valueBuffer.data()), reliableWriteContext, flags); if (SUCCEEDED(hr)) *systemErrorCode = NO_ERROR; else *systemErrorCode = WIN32_FROM_HRESULT(hr); } static QVector enumerateGattDescriptors( HANDLE hService, PBTH_LE_GATT_CHARACTERISTIC gattCharacteristic, int *systemErrorCode) { if (!gattFunctionsResolved) { *systemErrorCode = ERROR_NOT_SUPPORTED; return QVector(); } QVector 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(); } } } } 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(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(&valuePtr->Data[0]), valuePtr->DataSize/2); return valueString.toUtf8(); } return QByteArray(reinterpret_cast(&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(context); if (!qControllers->contains(target)) return; CharactericticValueEvent *e = new CharactericticValueEvent( reinterpret_cast(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(e); updateValueOfCharacteristic(characteristicEvent->m_handle, characteristicEvent->m_value, false); const QSharedPointer 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 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 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 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 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( &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 foundDescriptors = enumerateGattDescriptors( servicePrivate->hService, const_cast( &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( &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 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 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(); const QLowEnergyHandle charHandle = static_cast(data.gattCharacteristic.AttributeHandle); const QSharedPointer 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(); const QLowEnergyHandle charHandle = static_cast(data.gattCharacteristic.AttributeHandle); const QSharedPointer 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(); const QLowEnergyHandle descriptorHandle = static_cast(data.gattDescriptor.AttributeHandle); const QLowEnergyHandle charHandle = static_cast(data.gattDescriptor.CharacteristicHandle); const QSharedPointer 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(); const QLowEnergyHandle descriptorHandle = static_cast(data.gattDescriptor.AttributeHandle); const QLowEnergyHandle charHandle = static_cast(data.gattDescriptor.CharacteristicHandle); const QSharedPointer 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 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 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(); 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(); data.value = getGattCharacteristicValue( data.hService, &data.gattCharacteristic, &data.systemErrorCode); job.data = QVariant::fromValue(data); } break; case ThreadWorkerJob::WriteDescr: { WriteDescData data = job.data.value(); setGattDescriptorValue(data.hService, &data.gattDescriptor, data.newValue, &data.systemErrorCode); job.data = QVariant::fromValue(data); } break; case ThreadWorkerJob::ReadDescr: { ReadDescData data = job.data.value(); data.value = getGattDescriptorValue( data.hService, const_cast(&data.gattDescriptor), &data.systemErrorCode); job.data = QVariant::fromValue(data); } break; } m_jobs.removeFirst(); emit jobFinished(job); } QT_END_NAMESPACE