From 8cc71db6e2c0196f93f7c6ded6634b267b86e8f9 Mon Sep 17 00:00:00 2001 From: Dorian Zedler Date: Thu, 8 Oct 2020 20:01:21 +0200 Subject: [PATCH] initial commit --- QBluetoothLeUart.pri | 32 ++++ QBluetoothLeUart.pro | 34 ++++ QBluetoothLeUart.pro.user | 355 +++++++++++++++++++++++++++++++++++++ qbluetoothleuart.cpp | 278 +++++++++++++++++++++++++++++ qbluetoothleuart.h | 106 +++++++++++ qbluetoothleuartdevice.cpp | 34 ++++ qbluetoothleuartdevice.h | 35 ++++ 7 files changed, 874 insertions(+) create mode 100644 QBluetoothLeUart.pri create mode 100644 QBluetoothLeUart.pro create mode 100644 QBluetoothLeUart.pro.user create mode 100644 qbluetoothleuart.cpp create mode 100644 qbluetoothleuart.h create mode 100644 qbluetoothleuartdevice.cpp create mode 100644 qbluetoothleuartdevice.h diff --git a/QBluetoothLeUart.pri b/QBluetoothLeUart.pri new file mode 100644 index 0000000..13a4be7 --- /dev/null +++ b/QBluetoothLeUart.pri @@ -0,0 +1,32 @@ +!isEmpty(QBLUETOOTHLEUART_LIB):error("QBluetoothLeUart.pri already included") +QBLUETOOTHLEUART_LIB = 1 + +QBluetoothLeUart_QML { + QT += qml quickcontrols2 + DEFINES += QBluetoothLeUart_QML +} + +QT += core bluetooth + +TEMPLATE = lib +CONFIG += staticlib + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + qbluetoothleuart.cpp \ + qbluetoothleuartdevice.cpp + +HEADERS += \ + qbluetoothleuart.h \ + qbluetoothleuartdevice.h + +# Default rules for deployment. +unix { + target.path = $$[QT_INSTALL_PLUGINS]/generic +} +!isEmpty(target.path): INSTALLS += target diff --git a/QBluetoothLeUart.pro b/QBluetoothLeUart.pro new file mode 100644 index 0000000..f46c63d --- /dev/null +++ b/QBluetoothLeUart.pro @@ -0,0 +1,34 @@ +!isEmpty(QBLUETOOTHLEUART_LIB):error("QBluetoothLeUart.pri already included") +QBLUETOOTHLEUART_LIB = 1 + +CONFIG += QBluetoothLeUart_QML + +QBluetoothLeUart_QML { + QT += qml quickcontrols2 + DEFINES += QBluetoothLeUart_QML +} + +QT += core bluetooth + +TEMPLATE = lib +CONFIG += staticlib + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + qbluetoothleuart.cpp \ + qbluetoothleuartdevice.cpp + +HEADERS += \ + qbluetoothleuart.h \ + qbluetoothleuartdevice.h + +# Default rules for deployment. +unix { + target.path = $$[QT_INSTALL_PLUGINS]/generic +} +!isEmpty(target.path): INSTALLS += target diff --git a/QBluetoothLeUart.pro.user b/QBluetoothLeUart.pro.user new file mode 100644 index 0000000..f1c7229 --- /dev/null +++ b/QBluetoothLeUart.pro.user @@ -0,0 +1,355 @@ + + + + + + EnvironmentId + {8b2b329f-2b96-47e0-8e3b-213b44b4afec} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + *.md, *.MD, Makefile + false + true + + + + ProjectExplorer.Project.PluginSettings + + + true + true + true + true + true + + + 0 + true + + true + Builtin.Questionable + + true + Builtin.DefaultTidyAndClazy + 4 + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop Qt 5.12.6 GCC 64bit + Desktop Qt 5.12.6 GCC 64bit + qt.qt5.5126.gcc_64_kit + 0 + 0 + 0 + + true + 0 + /home/dorian/Qt/builds/build-QBluetoothLeUart-Desktop_Qt_5_12_6_GCC_64bit-Debug + /home/dorian/Qt/builds/build-QBluetoothLeUart-Desktop_Qt_5_12_6_GCC_64bit-Debug + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + 2 + 2 + + + true + 2 + /home/dorian/Qt/builds/build-QBluetoothLeUart-Desktop_Qt_5_12_6_GCC_64bit-Release + /home/dorian/Qt/builds/build-QBluetoothLeUart-Desktop_Qt_5_12_6_GCC_64bit-Release + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 2 + + + true + 0 + /home/dorian/Qt/builds/build-QBluetoothLeUart-Desktop_Qt_5_12_6_GCC_64bit-Profile + /home/dorian/Qt/builds/build-QBluetoothLeUart-Desktop_Qt_5_12_6_GCC_64bit-Profile + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + dwarf + + cpu-cycles + + + 250 + + -e + cpu-cycles + --call-graph + dwarf,4096 + -F + 250 + + -F + true + 4096 + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + kcachegrind + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + + 2 + + + ProjectExplorer.CustomExecutableRunConfiguration + + + false + + false + true + false + false + true + + + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/qbluetoothleuart.cpp b/qbluetoothleuart.cpp new file mode 100644 index 0000000..728e45d --- /dev/null +++ b/qbluetoothleuart.cpp @@ -0,0 +1,278 @@ +#include "qbluetoothleuart.h" + +QBluetoothLeUart::QBluetoothLeUart(QObject *parent) : QObject(parent) +{ + currentBluetoothDevice = nullptr; + bluetoothController = nullptr; + bluetoothService = nullptr; + + state = Idle; + + /* 1 Step: Bluetooth LE Device Discovery */ + this->bluetoothDeviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this); + + + /* Device Discovery Initialization */ + connect(this->bluetoothDeviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &QBluetoothLeUart::handleDeviceDiscovered); + connect(bluetoothDeviceDiscoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)), + this, SLOT(handleDeviceScanError(QBluetoothDeviceDiscoveryAgent::Error))); + connect(this->bluetoothDeviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, &QBluetoothLeUart::handleScanFinished); +} + +QBluetoothLeUart::~QBluetoothLeUart(){ + +} + +void QBluetoothLeUart::init() { +#ifdef QBluetoothLeUart_QML + qmlRegisterUncreatableType("de.itsblue.bluetoothleuart", 1, 0, "BluetoothDeviceInfo", "BluetoothDeviceInfo cannot be created"); + qmlRegisterType("de.itsblue.bluetoothleuart", 1, 0, "BluetoothLeUART"); + qRegisterMetaType("QBluetoothLeUart::BluetoothLeUartState"); + qRegisterMetaType("QBluetoothLeUart::BluetoothScanError"); +#endif +} + + +void QBluetoothLeUart::startScanningForDevices(){ + + foreach(QBluetoothLeUartDevice* oldDevice, this->availableDevices) + oldDevice->deleteLater(); + + this->availableDevices.clear(); + + setState(Scanning); + bluetoothDeviceDiscoveryAgent->start(); + + qDebug()<< "Searching for low energy devices..." ; +} + +void QBluetoothLeUart::stopScanningForDevices() { + this->bluetoothDeviceDiscoveryAgent->stop(); + this->setState(ScanFinished); +} + +void QBluetoothLeUart::handleDeviceDiscovered(const QBluetoothDeviceInfo &device) +{ + // Is it a BLE device? + if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) { + //qWarning() << "Discovered BLE Device: name: " << device.name() << " Address: " << device.address().toString(); + QBluetoothLeUartDevice *dev = new QBluetoothLeUartDevice(device, this); + availableDevices.append(dev); + emit this->foundNewDevice(dev); + emit this->avaliableDevicesChanged(this->availableDevices); + } +} + +void QBluetoothLeUart::handleScanFinished() +{ + if (availableDevices.size() == 0) + { + qWarning() << "No Low Energy devices found" << endl; + } + + setState(ScanFinished); +} + +QList QBluetoothLeUart::getAvailableDevices() { + return this->availableDevices; +} + +void QBluetoothLeUart::handleDeviceScanError(QBluetoothDeviceDiscoveryAgent::Error error) +{ + if (error == QBluetoothDeviceDiscoveryAgent::PoweredOffError) + emit this->scanningErrorOccured(AdapterTurnedOffError); + else if (error == QBluetoothDeviceDiscoveryAgent::InputOutputError) + emit this->scanningErrorOccured(InputOutputError); + else + emit this->scanningErrorOccured(UnknownError); +} + + +void QBluetoothLeUart::connectToDevice(QBluetoothLeUartDevice *device){ + + m_qvMeasurements.clear(); + + this->currentBluetoothDevice = device; + + if (bluetoothController) { + bluetoothController->disconnectFromDevice(); + delete bluetoothController; + bluetoothController = 0; + } + + /* 2 Step: QLowEnergyController */ + bluetoothController = new QLowEnergyController(currentBluetoothDevice->getDevice(), this); + bluetoothController ->setRemoteAddressType(QLowEnergyController::RandomAddress); + + connect(this->bluetoothController, &QLowEnergyController::serviceDiscovered, this, &QBluetoothLeUart::handleServiceDiscovered); + connect(this->bluetoothController, &QLowEnergyController::discoveryFinished, this, &QBluetoothLeUart::handleServiceScanDone); + connect(bluetoothController, SIGNAL(error(QLowEnergyController::Error)), + this, SLOT(handleControllerError(QLowEnergyController::Error))); + connect(this->bluetoothController, &QLowEnergyController::connected, this, &QBluetoothLeUart::handleDeviceConnected); + connect(this->bluetoothController, &QLowEnergyController::disconnected, this, &QBluetoothLeUart::handleDeviceDisconnected); + + /* Start connecting to device */ + bluetoothController->connectToDevice(); + setState(Connecting); +} + +void QBluetoothLeUart::disconnectFromDevice() { + this->bluetoothController->disconnectFromDevice(); + + disconnect(this->bluetoothController, &QLowEnergyController::serviceDiscovered, this, &QBluetoothLeUart::handleServiceDiscovered); + disconnect(this->bluetoothController, &QLowEnergyController::discoveryFinished, this, &QBluetoothLeUart::handleServiceScanDone); + disconnect(bluetoothController, SIGNAL(error(QLowEnergyController::Error)), + this, SLOT(handleControllerError(QLowEnergyController::Error))); + disconnect(this->bluetoothController, &QLowEnergyController::connected, this, &QBluetoothLeUart::handleDeviceConnected); + disconnect(this->bluetoothController, &QLowEnergyController::disconnected, this, &QBluetoothLeUart::handleDeviceDisconnected); + + this->bluetoothController->deleteLater(); +} + +void QBluetoothLeUart::handleDeviceDisconnected() +{ + this->setState(Idle); + qDebug() << "UART service disconnected"; + qWarning() << "Remote device disconnected"; +} + +void QBluetoothLeUart::handleDeviceConnected() +{ + qDebug() << "Device connected"; + bluetoothController->discoverServices(); + setState(Connected); +} + +void QBluetoothLeUart::handleControllerError(QLowEnergyController::Error error) +{ + qDebug() << "Cannot connect to remote device."; + qWarning() << "Controller Error:" << error; +} + +void QBluetoothLeUart::handleServiceDiscovered(const QBluetoothUuid &gatt){ + + qDebug() << "Found service with ID: " << gatt; + + if(gatt==QBluetoothUuid(QUuid(UARTSERVICEUUID))){ + foundValidUARTService =true; + qDebug() << "UART service found!"; + } +} + +void QBluetoothLeUart::handleServiceScanDone(){ + + delete bluetoothService; + bluetoothService=0; + + if(foundValidUARTService){ + qDebug() << "Connecting to UART service..."; + bluetoothService = bluetoothController->createServiceObject(QBluetoothUuid(QUuid(UARTSERVICEUUID)),this); + } + + if(!bluetoothService){ + qDebug() <<"UART service not found"; + this->disconnectFromDevice(); + return; + } + + /* 3 Step: Service Discovery */ + connect(this->bluetoothService, &QLowEnergyService::stateChanged, this, &QBluetoothLeUart::handleServiceStateChange); + connect(this->bluetoothService, &QLowEnergyService::characteristicChanged, this, &QBluetoothLeUart::handleServiceCharacteristicChange); + connect(this->bluetoothService, &QLowEnergyService::descriptorWritten, this, &QBluetoothLeUart::handleServiceDescriptorWritten); + + bluetoothService->discoverDetails(); + setState(ServiceFound); +} + +/* Slots for QLowEnergyService */ +void QBluetoothLeUart::handleServiceStateChange(QLowEnergyService::ServiceState s) +{ + + // A descriptoc can only be written if the service is in the ServiceDiscovered state + switch (s) { + case QLowEnergyService::ServiceDiscovered: + { + + //looking for the TX characteristic + const QLowEnergyCharacteristic TxChar = bluetoothService->characteristic(QBluetoothUuid(QUuid(TXUUID))); + if (!TxChar.isValid()){ + qDebug() << "Tx characteristic not found"; + this->disconnectFromDevice(); + return; + } + + //looking for the RX characteristic + const QLowEnergyCharacteristic RxChar = bluetoothService->characteristic(QBluetoothUuid(QUuid(RXUUID))); + if (!RxChar.isValid()) { + qDebug() << "Rx characteristic not found"; + this->disconnectFromDevice(); + return; + } + + + // Bluetooth LE spec Where a characteristic can be notified, a Client Characteristic Configuration descriptor + // shall be included in that characteristic as required by the Bluetooth Core Specification + // Tx notify is enabled + const QLowEnergyDescriptor m_notificationDescTx = TxChar.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + + if (m_notificationDescTx.isValid()) { + // enable notification + bluetoothService->writeDescriptor(m_notificationDescTx, QByteArray::fromHex("0100")); + setState(AcquireData); + emit this->connectedToDevice(); + } + + break; + } + default: + //nothing for now + break; + } +} + +void QBluetoothLeUart::handleServiceCharacteristicChange(const QLowEnergyCharacteristic &c,const QByteArray &value) +{ + // ignore any other characteristic change + if (c.uuid() != QBluetoothUuid(QUuid(TXUUID))) + return; + + emit dataReceived((QString) value); +} + +void QBluetoothLeUart::handleServiceDescriptorWritten(const QLowEnergyDescriptor &d, + const QByteArray &value) +{ + if (d.isValid() && d == bluetoothTransmissionDescriptor && value == QByteArray("0000")) { + //disabled notifications -> assume disconnect intent + bluetoothController->disconnectFromDevice(); + delete bluetoothService; + bluetoothService = 0; + } +} + + +void QBluetoothLeUart::sendData(QString s){ + + const QLowEnergyCharacteristic RxChar = bluetoothService->characteristic(QBluetoothUuid(QUuid(RXUUID))); + + qDebug()<< s; + QByteArray Data; + Data.append(s); + + bluetoothService->writeCharacteristic(RxChar, Data,QLowEnergyService::WriteWithoutResponse); +} + + + +void QBluetoothLeUart::setState(QBluetoothLeUart::BluetoothLeUartState newState) +{ + if (state == newState) + return; + + state = newState; + emit stateChanged(newState); +} + +QBluetoothLeUart::BluetoothLeUartState QBluetoothLeUart::getState() const { + return state; +} diff --git a/qbluetoothleuart.h b/qbluetoothleuart.h new file mode 100644 index 0000000..94794e4 --- /dev/null +++ b/qbluetoothleuart.h @@ -0,0 +1,106 @@ +#ifndef BLUETOOTHLEUART_H +#define BLUETOOTHLEUART_H + +#include +#include +#include +#include + +#ifdef QBluetoothLeUart_QML +#include +#endif + +#include "qbluetoothleuartdevice.h" + +#define UARTSERVICEUUID "6e400001-b5a3-f393-e0a9-e50e24dcca9e" +#define RXUUID "6e400002-b5a3-f393-e0a9-e50e24dcca9e" +#define TXUUID "6e400003-b5a3-f393-e0a9-e50e24dcca9e" + +class QBluetoothLeUart : public QObject +{ + Q_OBJECT + Q_PROPERTY(QList avaliableDevices READ getAvailableDevices NOTIFY avaliableDevicesChanged) + Q_PROPERTY(BluetoothLeUartState state READ getState NOTIFY stateChanged) + +public: + enum BluetoothLeUartState { + Idle = 0, + Scanning, + ScanFinished, + Connecting, + Connected, + ServiceFound, + AcquireData + }; + Q_ENUM(BluetoothLeUartState) + + enum BluetoothScanError { + UnknownError, + AdapterTurnedOffError, + InputOutputError + }; + Q_ENUM(BluetoothScanError); + + QBluetoothLeUart(QObject *parent = nullptr); + ~QBluetoothLeUart(); + + static void init(); + +private: + QBluetoothLeUartDevice *currentBluetoothDevice; + QBluetoothDeviceDiscoveryAgent *bluetoothDeviceDiscoveryAgent; + //QList m_qlDevices; + QList availableDevices; + //QList m_qlFoundDevices; + QVector m_qvMeasurements; + QLowEnergyController *bluetoothController; + QLowEnergyService *bluetoothService; + QLowEnergyDescriptor bluetoothTransmissionDescriptor; + bool foundValidUARTService; + + QBluetoothLeUart::BluetoothLeUartState state; + +public slots: + + /* Slots for user */ + Q_INVOKABLE QBluetoothLeUart::BluetoothLeUartState getState() const; + + Q_INVOKABLE void startScanningForDevices(); + Q_INVOKABLE void stopScanningForDevices(); + Q_INVOKABLE QList getAvailableDevices(); + Q_INVOKABLE void connectToDevice(QBluetoothLeUartDevice *device); + Q_INVOKABLE void sendData(QString s); + Q_INVOKABLE void disconnectFromDevice(); + +private slots: + void setState(QBluetoothLeUart::BluetoothLeUartState newState); + + /* Slots for QBluetothDeviceDiscoveryAgent */ + void handleDeviceDiscovered(const QBluetoothDeviceInfo&); + void handleScanFinished(); + void handleDeviceScanError(QBluetoothDeviceDiscoveryAgent::Error); + + /* Slots for QLowEnergyController */ + void handleServiceDiscovered(const QBluetoothUuid &); + void handleServiceScanDone(); + void handleControllerError(QLowEnergyController::Error); + void handleDeviceConnected(); + void handleDeviceDisconnected(); + + /* Slotes for QLowEnergyService */ + void handleServiceStateChange(QLowEnergyService::ServiceState s); + void handleServiceCharacteristicChange(const QLowEnergyCharacteristic &c, const QByteArray &value); + void handleServiceDescriptorWritten(const QLowEnergyDescriptor &d, const QByteArray &value); + +signals: + /* Signals for user */ + void stateChanged(QBluetoothLeUart::BluetoothLeUartState newState); + void avaliableDevicesChanged(QList avaliableDevices); + void foundNewDevice(QBluetoothLeUartDevice* device); + void scanningErrorOccured(QBluetoothLeUart::BluetoothScanError error); + void dataReceived(QString s); + void connectedToDevice(); + +}; + +#endif // BLUETOOTHLEUART_H diff --git a/qbluetoothleuartdevice.cpp b/qbluetoothleuartdevice.cpp new file mode 100644 index 0000000..ca58f7a --- /dev/null +++ b/qbluetoothleuartdevice.cpp @@ -0,0 +1,34 @@ +#include "qbluetoothleuartdevice.h" + +QBluetoothLeUartDevice::QBluetoothLeUartDevice(QBluetoothDeviceInfo info, QObject *parent) : QObject(parent) +{ + this->bluetoothDeviceInfo = info; +} + +QString QBluetoothLeUartDevice::getName() +{ + return bluetoothDeviceInfo.name(); +} + +QBluetoothDeviceInfo QBluetoothLeUartDevice::getDevice() +{ + return bluetoothDeviceInfo; +} + +QString QBluetoothLeUartDevice::getAddress() +{ +#ifdef Q_OS_MAC + // workaround for Core Bluetooth: + return m_device->deviceUuid().toString(); +#else + return bluetoothDeviceInfo.address().toString(); +#endif +} + +void QBluetoothLeUartDevice::setDevice(QBluetoothDeviceInfo device) +{ + if(device != this->bluetoothDeviceInfo) { + bluetoothDeviceInfo = device; + emit deviceChanged(); + } +} diff --git a/qbluetoothleuartdevice.h b/qbluetoothleuartdevice.h new file mode 100644 index 0000000..f7bc161 --- /dev/null +++ b/qbluetoothleuartdevice.h @@ -0,0 +1,35 @@ +#ifndef DEVICEINFO_H +#define DEVICEINFO_H + +#include +#include +#include +#include +#include + +class QBluetoothLeUartDevice: public QObject +{ + Q_OBJECT + Q_PROPERTY(QString name READ getName NOTIFY deviceChanged) + Q_PROPERTY(QString address READ getAddress NOTIFY deviceChanged) + +public: + QBluetoothLeUartDevice(QBluetoothDeviceInfo device, QObject *parent = nullptr); + + friend class QBluetoothLeUart; + + QString getName(); + QString getAddress(); + +protected: + QBluetoothDeviceInfo getDevice(); + +private: + void setDevice(QBluetoothDeviceInfo device); + QBluetoothDeviceInfo bluetoothDeviceInfo; + +signals: + void deviceChanged(); +}; + +#endif // DEVICEINFO_H