/*! * \copyright * Copyright (c) 2019-2020 Diality Inc. - All Rights Reserved. * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * * \file BluetoothInterface.h * \author (last) Behrouz NematiPour * \author (original) Behrouz NematiPour * \date (last) 22-Aug-2021 * \date (original) 22-Aug-2021 */ #include "BluetoothInterface.h" // Qt #include #include #include #include // Project #include "Logger.h" // namespace using namespace Bluetooth; using namespace Model; /*! * \brief BluetoothInterface::BluetoothInterface * \details Constructor * \param parent - QObject parent owner object. * Qt handles the children destruction by their parent objects life-cycle. */ BluetoothInterface::BluetoothInterface(QObject *parent) : QObject(parent) { _local = new QBluetoothLocalDevice (this); _agent = new QBluetoothDeviceDiscoveryAgent (this); startTimer(_interval); } /*! * \brief BluetoothInterface::init * \details Initializes the class * \return False if it has been called before. */ bool BluetoothInterface::init() { if ( _init ) return false; _init = true; // runs in BluetoothInterface thread initConnections(); LOG_DEBUG("UI," + tr("%1 Initialized").arg(metaObject()->className())); return true; } /*! * \brief BluetoothInterface::init * \details Initialized the Class by calling the init() method first * And initializes the thread vThread by calling initThread * on success init(). * \param vThread - the thread * \return returns the return value of the init() method */ bool BluetoothInterface::init(QThread &vThread) { if ( ! init() ) return false; initThread(vThread); return true; } /*! * \brief BluetoothInterface::quit * \details quits the class * Calls quitThread */ void BluetoothInterface::quit() { // coco begin validated: Application termination is not correctly done in coco!!! // it has been tested and works perfectly fine in normal run. quitThread(); // validated } void BluetoothInterface::onDestroy() { qDebug() << "Destroy"; delete _local; delete _agent; } // coco end /*! * \brief BluetoothInterface::initConnections * \details Initializes the required signal/slot connection between this class and other objects * to be able to communicate. */ void BluetoothInterface::initConnections() { connect(this , &BluetoothInterface :: destroyed , this , &BluetoothInterface ::onDestroy ); // Local connections connect(_local , &QBluetoothLocalDevice :: pairingFinished , this , &BluetoothInterface ::onLocalPairingFinish ); connect(_local , &QBluetoothLocalDevice :: pairingDisplayConfirmation , this , &BluetoothInterface ::onLocalPairingDisplayConfirmation ); connect(_local , &QBluetoothLocalDevice :: pairingDisplayPinCode , this , &BluetoothInterface ::onLocalPairingDisplayPinCode ); connect(_local , &QBluetoothLocalDevice :: deviceConnected , this , &BluetoothInterface ::onLocalDeviceConnect ); connect(_local , &QBluetoothLocalDevice :: deviceDisconnected , this , &BluetoothInterface ::onLocalDeviceDisconnect ); connect(_local , &QBluetoothLocalDevice :: error , this , &BluetoothInterface ::onLocalError ); // Agent connections connect(_agent , &QBluetoothDeviceDiscoveryAgent ::deviceDiscovered , this , &BluetoothInterface ::onAgentDiscoverDevice ); connect(_agent , &QBluetoothDeviceDiscoveryAgent :: finished , this , &BluetoothInterface ::onAgentDiscoverFinish ); connect(_agent , &QBluetoothDeviceDiscoveryAgent :: canceled , this , &BluetoothInterface ::onAgentDiscoverCancel ); connect(_agent , static_cast(&QBluetoothDeviceDiscoveryAgent::error), this , &BluetoothInterface ::onAgentDiscoverError ); } /*! * \brief BluetoothInterface::initThread * \details Moves this object into the thread vThread. * And checks that this method is called from main thread. * Also connects quitThread to application aboutToQuit. * \param vThread - the thread */ void BluetoothInterface::initThread(QThread &vThread) { // runs in main thread Q_ASSERT_X(QThread::currentThread() == qApp->thread() , __func__, "The Class initialization must be done in Main Thread" ); _thread = &vThread; _thread->setObjectName(QString("%1_Thread").arg(metaObject()->className())); connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(quit())); _thread->start(); moveToThread(_thread); } /*! * \brief BluetoothInterface::quitThread * \details Moves this object to main thread to be handled by QApplicaiton * And to be destroyed there. */ void BluetoothInterface::quitThread() { // coco begin validated: Application termination is not correctly done in coco!!! // it has been tested and works perfectly fine in normal run. if ( ! _thread ) return; // runs in thread moveToThread(qApp->thread()); // validated } // coco end // ~~~~~~~~~~~ Local #define NOTIFY_LOCAL_CONNECT notifyStateChange(MBluetooth(MBluetooth::eIS_Local_Connect , vAddress.toString()) ); #define NOTIFY_LOCAL_ERROR notifyStateChange(MBluetooth(MBluetooth::eIS_Local_Error_Unknown )); #define NOTIFY_LOCAL_ERROR_IO notifyStateChange(MBluetooth(MBluetooth::eIS_Local_Error_IO )); #define NOTIFY_LOCAL_ERROR_OFF notifyStateChange(MBluetooth(MBluetooth::eIS_Local_Error_Off )); #define NOTIFY_LOCAL_ERROR_INVALID notifyStateChange(MBluetooth(MBluetooth::eIS_Local_Error_Invalid )); #define NOTIFY_LOCAL_DISCONNECT notifyStateChange(MBluetooth(MBluetooth::eIS_Local_Disconnect , vAddress.toString()) ); // ~~~~~~~~~~~ Pair #define NOTIFY_PAIR_START notifyStateChange(MBluetooth(MBluetooth::eIS_Pair_Start , _device->remoteAddress().toString())); #define NOTIFY_PAIR_DONE notifyStateChange(MBluetooth(MBluetooth::eIS_Pair_Done , vAddress.toString(), "", "", vPairing)); #define NOTIFY_PAIR_CONFIRM notifyStateChange(MBluetooth(MBluetooth::eIS_Pair_Confirm , vAddress.toString(), "", vPin ) ); #define NOTIFY_PAIR_PINCODE notifyStateChange(MBluetooth(MBluetooth::eIS_Pair_PinCode , vAddress.toString(), "", vPin ) ); #define NOTIFY_PAIR_ERROR notifyStateChange(MBluetooth(MBluetooth::eIS_Pair_Error )); // ~~~~~~~~~~~ Scan #define NOTIFY_SCAN_DISCOVER notifyStateChange(MBluetooth(MBluetooth::eIS_Scan_Discover , \ vInfo.address().toString() , \ vInfo.name(), "" , \ _local->pairingStatus(vInfo.address()) , \ 0 , \ vInfo.isValid() , \ vInfo.deviceUuid().toString() )); #define NOTIFY_SCAN_FOUND _model = MBluetooth(MBluetooth::eIS_Scan_Found , \ _info.address().toString() , \ _info.name(), "" , \ _local->pairingStatus(vInfo.address()) , \ 0 , \ _info.isValid() , \ _info.deviceUuid().toString() );\ notifyStateChange(_model ); #define NOTIFY_SCAN_START notifyStateChange(MBluetooth(MBluetooth::eIS_Scan_Start )); #define NOTIFY_SCAN_REJECT notifyStateChange(MBluetooth(MBluetooth::eIS_Scan_Reject )); #define NOTIFY_SCAN_NOTFOUND notifyStateChange(MBluetooth(MBluetooth::eIS_Scan_NotFound ,"","" )); #define NOTIFY_SCAN_STOP notifyStateChange(MBluetooth(MBluetooth::eIS_Scan_Stop )); #define NOTIFY_SCAN_DONE notifyStateChange(MBluetooth(MBluetooth::eIS_Scan_Done )); #define NOTIFY_SCAN_DETAIL notifyStateChange(MBluetooth(MBluetooth::eIS_Scan_Detail , \ _info.address().toString() , \ _info.name() )); // ~~~~~~~~~ Device #define NOTIFY_DEVICE_INIT_ERROR notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Error_Init )); #define NOTIFY_DEVICE_INIT notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Init , \ _info.address().toString() , \ _info.name() )); #define NOTIFY_DEVICE_START notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Start , \ _info.address().toString() , \ _info.name() )); #define NOTIFY_DEVICE_CONNECT notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Connect , \ _info.address().toString() , \ _info.name() )); #define NOTIFY_DEVICE_ERROR notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Error , \ _info.address().toString() , \ _info.name() , \ "",0, vError, false )); #define NOTIFY_DEVICE_DISCONNECT notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Disconnect , \ _info.address().toString() , \ _info.name() )); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Local Device Slots // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void BluetoothInterface::onLocalDeviceConnect(const QBluetoothAddress &vAddress) { NOTIFY_LOCAL_CONNECT } void BluetoothInterface::onLocalDeviceDisconnect(const QBluetoothAddress &vAddress) { NOTIFY_LOCAL_DISCONNECT } void BluetoothInterface::onLocalPairingFinish(const QBluetoothAddress &vAddress, QBluetoothLocalDevice::Pairing vPairing) { NOTIFY_PAIR_DONE //_local->pairingConfirmation(true); _device->discoverServices(); } void BluetoothInterface::onLocalPairingDisplayConfirmation(const QBluetoothAddress &vAddress, const QString &vPin) { NOTIFY_PAIR_CONFIRM //_local->pairingConfirmation(true); } void BluetoothInterface::onLocalPairingDisplayPinCode(const QBluetoothAddress &vAddress, const QString &vPin) { NOTIFY_PAIR_PINCODE //_local->pairingConfirmation(true); } void BluetoothInterface::onLocalError(QBluetoothLocalDevice::Error vError) { switch (vError) { case QBluetoothLocalDevice::PairingError: NOTIFY_PAIR_ERROR break; default : NOTIFY_LOCAL_ERROR break; } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Discovery Agent Slots // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void BluetoothInterface::onAgentDiscoverDevice(const QBluetoothDeviceInfo &vInfo) { NOTIFY_SCAN_DISCOVER if (vInfo.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) { if ( vInfo.name().startsWith("BP7000") || vInfo.name().startsWith("BLEsmart") || vInfo.name().startsWith("BLESmart") ) { _info = QBluetoothDeviceInfo(vInfo.address(), vInfo.name(), QBluetoothDeviceInfo::HealthBloodPressureMonitor); NOTIFY_SCAN_FOUND if (_agent->isActive()) _agent->stop(); } } } void BluetoothInterface::onAgentDiscoverFinish() { NOTIFY_SCAN_DONE discoverFinish(); } void BluetoothInterface::onAgentDiscoverCancel() { NOTIFY_SCAN_STOP discoverFinish(); } void BluetoothInterface::onAgentDiscoverError(QBluetoothDeviceDiscoveryAgent::Error vError) { switch (vError) { case QBluetoothDeviceDiscoveryAgent::PoweredOffError : NOTIFY_LOCAL_ERROR_OFF break; case QBluetoothDeviceDiscoveryAgent::InputOutputError : NOTIFY_LOCAL_ERROR_IO break; default : NOTIFY_LOCAL_ERROR break; } } void BluetoothInterface::doScan() { if (_agent && _agent->isActive()) { NOTIFY_SCAN_REJECT return; } emit didstart(); } void BluetoothInterface::onstart() { _info = QBluetoothDeviceInfo(); // reset the device _local->powerOn(); if (! _local->isValid() ) { NOTIFY_LOCAL_ERROR_INVALID return; } qDebug() << _local->address(); qDebug() << _local->connectedDevices(); qDebug() << _local->hostMode(); qDebug() << _local->isValid(); qDebug() << _local->name(); _agent->setLowEnergyDiscoveryTimeout(5000); NOTIFY_SCAN_START _agent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); } bool BluetoothInterface::discoverFinish() { if (! _local->isValid() ) { NOTIFY_LOCAL_ERROR_INVALID return false; } if ( ! _info.isValid() ) { NOTIFY_SCAN_NOTFOUND return false; } NOTIFY_SCAN_DETAIL _device = QLowEnergyController::createCentral(_info, this); if (_device) NOTIFY_DEVICE_INIT else { NOTIFY_DEVICE_INIT_ERROR return false; } QObject::connect(_device, &QLowEnergyController::connected, [=](){ NOTIFY_DEVICE_CONNECT // TODO: If the device is paired it can't be connected again !!!!!!!!!! // -------------------------------------------------------------------- // if ( _model.devicePair == QBluetoothLocalDevice::Unpaired ) { // _local->requestPairing(_device->remoteAddress(), QBluetoothLocalDevice::AuthorizedPaired); // qDebug() << " ~~~~~ Connected Request Paring"; // NOTIFY_PAIR_START // } // else { // qDebug() << " ~~~~~ Connected Discover Services"; _device->discoverServices(); // } }); QObject::connect(_device, &QLowEnergyController::serviceDiscovered, [=](const QBluetoothUuid &service){ QLowEnergyService *m_service = _device->createServiceObject(service, this); qDebug() << " ~~~~~ Controller Service : " << m_service->serviceUuid() << m_service->serviceName(); }); QObject::connect(_device, &QLowEnergyController::discoveryFinished, [=](){ qDebug() << " ~~~~~ Controller Discovery Finished"; _device->disconnectFromDevice(); }); QObject::connect(_device, &QLowEnergyController::disconnected, [=](){ NOTIFY_DEVICE_DISCONNECT }); QObject::connect(_device, QOverload::of(&QLowEnergyController::error),[=](QLowEnergyController::Error vError){ NOTIFY_DEVICE_ERROR }); _device->connectToDevice(); NOTIFY_DEVICE_START return true; } void BluetoothInterface::notifyStateChange(const MBluetooth &vData) { _state = vData.state; emit didStateChange(vData); } void BluetoothInterface::timerEvent(QTimerEvent *) { if ( _bpRead ) { } }