From 43eebc049aef3b858dd1d38ebe4bf9b395891722 Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov (VMware)" Date: Tue, 28 Nov 2017 17:26:09 +0100 Subject: Add a thread for QLowEnergyControllerPrivateWin32 Create a re-usable thread ('thread') and worker ('treadWorker') in QLowEnergyControllerPrivateWin32 that have the same lifespan as the controller object. Add "job" support (ThreadWorkerJob) for the thread so that jobs are scheduled and executed sequentially. Each job should have a data struct. For writing that is WriteCharData. Handle writing of characteristics in: QLowEnergyControllerPrivateWin32::writeCharacteristic() QLowEnergyControllerPrivateWin32::jobFinished() ThreadWorker::runPendingJob() The above jobFinished() and runPendingJob() use a `switch` to determine the ThreadWorkerJob type. Change-Id: I3331e0d4adc29565a88fd792f9a54833881ea694 Reviewed-by: Denis Shienkov Reviewed-by: Oliver Wolff --- src/bluetooth/qlowenergycontroller_win.cpp | 126 ++++++++++++++++++++++------- src/bluetooth/qlowenergycontroller_win_p.h | 43 ++++++++++ 2 files changed, 140 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/bluetooth/qlowenergycontroller_win.cpp b/src/bluetooth/qlowenergycontroller_win.cpp index f7a1611f..27bfb7c7 100644 --- a/src/bluetooth/qlowenergycontroller_win.cpp +++ b/src/bluetooth/qlowenergycontroller_win.cpp @@ -45,11 +45,10 @@ #include // for open modes #include #include +#include #include // for std::max -#include - #include QT_BEGIN_NAMESPACE @@ -690,12 +689,22 @@ QLowEnergyControllerPrivateWin32::QLowEnergyControllerPrivateWin32() qCWarning(QT_BT_WINDOWS) << "LE is not supported on this OS"; return; } + + 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(); } QLowEnergyControllerPrivateWin32::~QLowEnergyControllerPrivateWin32() { QMutexLocker locker(&controllersGuard); qControllers()->removeAll(this); + if (thread) + thread->quit(); } void QLowEnergyControllerPrivateWin32::init() @@ -1020,17 +1029,20 @@ void QLowEnergyControllerPrivateWin32::writeCharacteristic( QLowEnergyService::WriteMode mode) { Q_ASSERT(!service.isNull()); - if (!service->characteristicList.contains(charHandle)) - return; - int systemErrorCode = NO_ERROR; + if (!service->characteristicList.contains(charHandle)) { + service->setError(QLowEnergyService::CharacteristicWriteError); + return; + } - const HANDLE hService = openSystemService( - remoteDevice, service->uuid, QIODevice::ReadWrite, &systemErrorCode); + WriteCharData data; + data.systemErrorCode = NO_ERROR; + data.hService = openSystemService( + remoteDevice, service->uuid, QIODevice::ReadWrite, &data.systemErrorCode); - if (systemErrorCode != NO_ERROR) { + if (data.systemErrorCode != NO_ERROR) { qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString() - << ":" << qt_error_string(systemErrorCode); + << ":" << qt_error_string(data.systemErrorCode); service->setError(QLowEnergyService::CharacteristicWriteError); return; } @@ -1038,35 +1050,58 @@ void QLowEnergyControllerPrivateWin32::writeCharacteristic( const QLowEnergyServicePrivate::CharData &charDetails = service->characteristicList[charHandle]; - BTH_LE_GATT_CHARACTERISTIC gattCharacteristic = recoverNativeLeGattCharacteristic( + data.gattCharacteristic = recoverNativeLeGattCharacteristic( service->startHandle, charHandle, charDetails); - const DWORD flags = (mode == QLowEnergyService::WriteWithResponse) + data.flags = (mode == QLowEnergyService::WriteWithResponse) ? BLUETOOTH_GATT_FLAG_NONE : BLUETOOTH_GATT_FLAG_WRITE_WITHOUT_RESPONSE; - // TODO: If a device is not connected, this function will block - // for some time. So, need to re-implement of writeCharacteristic() - // with use QFutureWatcher. - setGattCharacteristicValue(hService, &gattCharacteristic, - newValue, flags, &systemErrorCode); - closeSystemDevice(hService); + ThreadWorkerJob job; + job.operation = ThreadWorkerJob::WriteChar; + data.newValue = newValue; + data.mode = mode; + job.data = QVariant::fromValue(data); - if (systemErrorCode != NO_ERROR) { - qCWarning(QT_BT_WINDOWS) << "Unable to set value for characteristic" - << charDetails.uuid.toString() - << "of the service" << service->uuid.toString() - << ":" << qt_error_string(systemErrorCode); - service->setError(QLowEnergyService::CharacteristicWriteError); - return; - } + QMetaObject::invokeMethod(threadWorker, "putJob", Qt::QueuedConnection, + Q_ARG(ThreadWorkerJob, job)); +} - updateValueOfCharacteristic(charHandle, newValue, false); +void QLowEnergyControllerPrivateWin32::jobFinished(const ThreadWorkerJob &job) +{ + switch (job.operation) { + case ThreadWorkerJob::WriteChar: + { + const WriteCharData data = job.data.value(); + closeSystemDevice(data.hService); + 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 (mode == QLowEnergyService::WriteWithResponse) { - const QLowEnergyCharacteristic ch(service, charHandle); - emit service->characteristicWritten(ch, newValue); + if (data.mode == QLowEnergyService::WriteWithResponse) { + const QLowEnergyCharacteristic ch = characteristicForHandle(charHandle); + emit service->characteristicWritten(ch, data.newValue); + } + } + break; + case ThreadWorkerJob::ReadChar: + case ThreadWorkerJob::WriteDescr: + case ThreadWorkerJob::ReadDescr: + break; } + + QMetaObject::invokeMethod(threadWorker, "runPendingJob", Qt::QueuedConnection); } void QLowEnergyControllerPrivateWin32::readDescriptor( @@ -1219,4 +1254,37 @@ void QLowEnergyControllerPrivateWin32::addToGenericAttributeList(const QLowEnerg 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: + case ThreadWorkerJob::WriteDescr: + case ThreadWorkerJob::ReadDescr: + break; + } + + m_jobs.removeFirst(); + emit jobFinished(job); +} + QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_win_p.h b/src/bluetooth/qlowenergycontroller_win_p.h index 9c2d4f92..64f824e6 100644 --- a/src/bluetooth/qlowenergycontroller_win_p.h +++ b/src/bluetooth/qlowenergycontroller_win_p.h @@ -59,8 +59,47 @@ #include "qlowenergycontroller.h" #include "qlowenergycontrollerbase_p.h" +#include + QT_BEGIN_NAMESPACE +class QThread; +class QLowEnergyControllerPrivateWin32; + +class ThreadWorkerJob +{ +public: + enum Operation { WriteChar, ReadChar, WriteDescr, ReadDescr }; + Operation operation; + QVariant data; +}; + +Q_DECLARE_METATYPE(ThreadWorkerJob) + +struct WriteCharData +{ + QByteArray newValue; + QLowEnergyService::WriteMode mode; + HANDLE hService; + DWORD flags; + BTH_LE_GATT_CHARACTERISTIC gattCharacteristic; + int systemErrorCode; +}; + +Q_DECLARE_METATYPE(WriteCharData) + +class ThreadWorker : public QObject +{ + Q_OBJECT +public: + Q_INVOKABLE void putJob(const ThreadWorkerJob &job); + Q_INVOKABLE void runPendingJob(); +signals: + void jobFinished(const ThreadWorkerJob &job); +private: + QVector m_jobs; +}; + class QLowEnergyServiceData; extern void registerQLowEnergyControllerMetaType(); @@ -105,9 +144,13 @@ public: void addToGenericAttributeList(const QLowEnergyServiceData &service, QLowEnergyHandle startHandle) override; +public slots: + void jobFinished(const ThreadWorkerJob &job); protected: void customEvent(QEvent *e); private: + QThread *thread = nullptr; + ThreadWorker *threadWorker = nullptr; QString deviceSystemPath; }; -- cgit v1.2.3