/*! * \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" #include "types.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 if ( _local ) _local ->deleteLater(); if ( _agent ) _agent ->deleteLater(); if ( _device ) _device ->deleteLater(); } // 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() { // Local connections 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 #define NOTIFY_IDLE notifyStateChange(MBluetooth(MBluetooth::eIS_Idle )); #define NOTIFY_CLOSE notifyStateChange(MBluetooth(MBluetooth::eIS_Close )); // ~~~~~~~~~~~ Local #define NOTIFY_LOCAL_INIT notifyStateChange(MBluetooth(MBluetooth::eIS_Local_Init , \ _local->address().toString() , \ _local->name(),0,1 )); #define NOTIFY_LOCAL_CONNECT notifyStateChange(MBluetooth(MBluetooth::eIS_Local_Connect , vAddress.toString()) ); #define NOTIFY_LOCAL_ERROR notifyStateChange(MBluetooth(MBluetooth::eIS_Local_Error )); #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 notifyStateChange(MBluetooth(MBluetooth::eIS_Scan_Found , \ _temp.address().toString() , \ _temp.name(), "" , \ _local->pairingStatus(_temp.address()) , \ 0 , \ _temp.isValid() , \ _temp.deviceUuid().toString() )); #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 )); // ~~~~~~~~~ Device #define NOTIFY_DEVICE_INIT_ERROR notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Error_Init )); #define NOTIFY_DEVICE_INIT notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Init , \ _device->remoteAddress().toString() , \ _device->remoteName() )); #define NOTIFY_DEVICE_START notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Start , \ _device->remoteAddress().toString() , \ _device->remoteName() )); #define NOTIFY_DEVICE_CONNECT notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Connect , \ _device->remoteAddress().toString() , \ _device->remoteName() )); #define NOTIFY_DEVICE_DONE notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Done , \ _device->remoteAddress().toString() , \ _device->remoteName() )); #define NOTIFY_DEVICE_ERROR notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Error , \ _device->remoteAddress().toString() , \ _device->remoteName() , \ "",0, vError, false )); #define NOTIFY_DEVICE_DISCONNECT notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Disconnect , \ _device->remoteAddress().toString() , \ _device->remoteName() )); #define NOTIFY_SERVICE_START notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Start )); #define NOTIFY_SERVICE_DISCOVER notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Discover )); #define NOTIFY_SERVICE_DETAILS(vS) notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Detail , \ _device->remoteAddress().toString() , \ _device->remoteName(), "" , \ _local->pairingStatus(_device->remoteAddress()), 0, 1 , \ vS->serviceUuid().toString() , \ vS->serviceName() )); #define NOTIFY_SERVICE_ERROR notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Error , \ _device->remoteAddress().toString() , \ _device->remoteName(), "" , \ _local->pairingStatus(_device->remoteAddress()), vError, 0 , \ service->serviceUuid().toString() , \ service->serviceName() )); #define NOTIFY_SERVICE_DONE notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Done )); #define NOTIFY_DETAILS_ERROR notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Detail_Error , \ _device->remoteAddress().toString() , \ _device->remoteName(), "" , \ _local->pairingStatus(_device->remoteAddress()), vState, 0 , \ service->serviceUuid().toString() , \ service->serviceName() )); #define NOTIFY_DETAILS_INVALID notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Detail_Invalid )); #define NOTIFY_SERVICE_DETAILS_DONE notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Detail_Done , \ _device->remoteAddress().toString() , \ _device->remoteName(), "" , \ _local->pairingStatus(_device->remoteAddress()), vState, 0 , \ service->serviceUuid().toString() , \ service->serviceName() )); /* #define NOTIFY_SERVICE_DISCOVER notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Discover , \ _service->serviceUuid().toString() , \ _service->serviceName() )); */ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Public interface to initialize and start the scan // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /*! * \brief BluetoothInterface::onstart * \details the thread safe slot to be called when the public function start is called. * initializes the local Bluetooth device _local and setups up the interface. */ void BluetoothInterface::onstart() { _local->powerOn(); if ( ! isLocalValid() ) return; quitDevice(); _agent->setLowEnergyDiscoveryTimeout(5000); NOTIFY_LOCAL_INIT NOTIFY_IDLE } /*! * \brief BluetoothInterface::ondoScan * \details the thread safe slot to be called when the public function doScan is called. * initializes/validates the discovery agent _agent and setups it up to start the discovery. */ void BluetoothInterface::ondoScan() { if (_agent && _agent->isActive()) { NOTIFY_SCAN_REJECT return; } NOTIFY_SCAN_START _agent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Local Device Slots // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Set of notifications on each event /*! * \brief BluetoothInterface::onLocalDeviceConnect * \details Notifies the observers (view: VBluetooth) that the Local Device Connected * \param vAddress */ void BluetoothInterface::onLocalDeviceConnect (const QBluetoothAddress &vAddress ) { NOTIFY_LOCAL_CONNECT } /*! * \brief BluetoothInterface::onLocalDeviceDisconnect * \details Notifies the observers (view: VBluetooth) that the Local Device Disconnected * \param vAddress */ void BluetoothInterface::onLocalDeviceDisconnect (const QBluetoothAddress &vAddress ) { NOTIFY_LOCAL_DISCONNECT } /*! * \brief BluetoothInterface::onLocalError * \details Notifies the observers (view: VBluetooth) that the local adapter has error * \param vError - The error */ void BluetoothInterface::onLocalError ( QBluetoothLocalDevice::Error /*vError*/ ) { NOTIFY_LOCAL_ERROR quitDevice(); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Discovery Agent Slots // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /*! * \brief BluetoothInterface::onAgentDiscoverDevice * \details The slot to handle each discovered Bluetooth Low Energy device. * if the discovered BLE device , which its information provided in vInfo, has any identified keyword, the scan/discovery stops if it is still active, and * notifies the observers (view: VBluetooth) that a device found. * \param vInfo * \note If multiple supported devices are near, the first one will be identified as found. */ void BluetoothInterface::onAgentDiscoverDevice(const QBluetoothDeviceInfo &vInfo) { NOTIFY_SCAN_DISCOVER if (vInfo.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) { if ( isDeviceSupported( vInfo.name() ) ) { _temp = QBluetoothDeviceInfo(vInfo.address(), vInfo.name(), QBluetoothDeviceInfo::HealthBloodPressureMonitor); NOTIFY_SCAN_FOUND if (_agent->isActive()) _agent->stop(); } } } /*! * \brief BluetoothInterface::onAgentDiscoverError * \details Notifies the observers (view: VBluetooth) that the discovery agent has error, during discovery. * \param vError - The discovery agent error */ 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; } if ( _device ) _device->disconnectFromDevice(); } /*! * \brief BluetoothInterface::onAgentDiscoverFinish * \details Notifies the observers (view: VBluetooth) that the discovery agent has done discovery, and * starts gathering details and setup the discovered device. */ void BluetoothInterface::onAgentDiscoverFinish() { NOTIFY_SCAN_DONE connectToDevice(); } /*! * \brief BluetoothInterface::onAgentDiscoverCancel * \details Notifies the observers (view: VBluetooth) that the discovery agent has stopped discovery, and * starts gathering details and setup the discovered device. * \note this slot is called when the discovery stopped before it is finished (currently by first supported found device). */ void BluetoothInterface::onAgentDiscoverCancel() { NOTIFY_SCAN_STOP connectToDevice(); } /*! * \brief BluetoothInterface::connectToDevice * \return false if * the local Bluetooth device _local is not valid * the device info _temp is not valid. * notifies the observers (view: VBluetooth) that the state is MBluetooth::eIS_Device_Start, * on successfully connecting initializing the remove Bluetooth device _device. */ bool BluetoothInterface::connectToDevice() { if ( ! isLocalValid() ) return false; if ( ! isInfoValid () ) return false; if ( ! initDevice () ) return false; NOTIFY_DEVICE_START _device->connectToDevice(); return true; } /*! * \brief BluetoothInterface::discoverServices * \details The function to be called to start retrieving the remote Bluetooth device _device's services, and * notifies the observers (view: VBluetooth) that the state is MBluetooth::eIS_Service_Start. */ void BluetoothInterface::discoverServices() { NOTIFY_SERVICE_START _device->discoverServices(); } /*! * \brief BluetoothInterface::onDeviceConnect * \details The slot to be called when the remote Bluetooth device _device is connected to start to retrieve its services, * by calling discoverServices(). * notifies the observers (view: VBluetooth) that the state is MBluetooth::eIS_Device_Connect. */ void BluetoothInterface::onDeviceConnect() { NOTIFY_DEVICE_CONNECT discoverServices(); } /*! * \brief BluetoothInterface::onDeviceDisconnect * \details The slot to be called when the remote Bluetooth device _device is disconnected, and * notifies the observers (view: VBluetooth) that the state is MBluetooth::eIS_Device_Disconnect. */ void BluetoothInterface::onDeviceDisconnect() { NOTIFY_DEVICE_DISCONNECT } void BluetoothInterface::onDeviceDiscoverService(const QBluetoothUuid &vService) { initServices(vService); } void BluetoothInterface::onDeviceDiscoverFinish() { NOTIFY_SERVICE_DISCOVER discoverServicesDetails(); } void BluetoothInterface::discoverServicesDetails() { if ( _serviceDeviceInformation ) { NOTIFY_SERVICE_DETAILS (_serviceDeviceInformation ) _serviceDeviceInformation ->discoverDetails(); } if ( _serviceCurrentTime ) { NOTIFY_SERVICE_DETAILS (_serviceCurrentTime ) qDebug() << _serviceCurrentTime ->type(); _serviceCurrentTime ->discoverDetails(); } if ( _serviceBloodPressure ) { NOTIFY_SERVICE_DETAILS (_serviceBloodPressure ) qDebug() << _serviceBloodPressure ->type(); _serviceBloodPressure ->discoverDetails(); } if ( _serviceBattery ) { NOTIFY_SERVICE_DETAILS (_serviceBattery ) qDebug() << _serviceBattery ->type(); _serviceBattery ->discoverDetails(); } } /*! * \brief BluetoothInterface::onDeviceError * \details The slot to be called when the remote Bluetooth device _device has an error vError, and * notifies the observers (view: VBluetooth) that the state is MBluetooth::eIS_Device_Error. * \param vError */ void BluetoothInterface::onDeviceError(QLowEnergyController::Error vError) { NOTIFY_DEVICE_ERROR } void BluetoothInterface::onServiceCharacteristicChanged(const QLowEnergyCharacteristic &vCharacteristic, const QByteArray &vValue) { qDebug() << " ..... Service Charc C:" << vCharacteristic.name() << vValue; if (vCharacteristic.uuid() == QBluetoothUuid(QBluetoothUuid::BloodPressureMeasurement)) { parseMeasurement(vValue); } } void BluetoothInterface::parseMeasurement(const QByteArray &byteArray) { MeasurementData measurement; // const uint8_t *d = reinterpret_cast(byteArray.constData()); const uchar *d = reinterpret_cast(byteArray.constData()); measurement.flags = *d; measurement.systolic = d[1]; measurement.diastolic = d[3]; measurement.mean_arterial_pressure_value= d[5]; measurement.year = d[7]; measurement.month = d[9]; measurement.day = d[10]; measurement.hour = d[11]; measurement.minute = d[12]; measurement.second = d[13]; measurement.pulse_rate = d[14]; measurement.user_id = d[16]; measurement.measurement_status = d[17]; qDebug() << measurement.flags << measurement.systolic << measurement.diastolic << measurement.mean_arterial_pressure_value << measurement.year << measurement.month << measurement.day << measurement.hour << measurement.minute << measurement.second << measurement.pulse_rate << measurement.user_id << measurement.measurement_status ; // emit didReceiveBPMeasurement(measurement); } /*! * \brief BluetoothInterface::initConnectionsDevice * \details sets up the remote Bluetooth device _device connections. */ void BluetoothInterface::initConnectionsDevice() { // Device controller connect( _device, SIGNAL( connected ( )), this , SLOT(onDeviceConnect ( ))); connect( _device, SIGNAL( error (QLowEnergyController::Error )), this , SLOT(onDeviceError (QLowEnergyController::Error ))); connect( _device, SIGNAL( disconnected ( )), this , SLOT(onDeviceDisconnect ( ))); // Device Services connect( _device, SIGNAL(serviceDiscovered (const QBluetoothUuid &)), this , SLOT(onDeviceDiscoverService(const QBluetoothUuid &))); connect( _device, SIGNAL(discoveryFinished ()), this , SLOT(onDeviceDiscoverFinish ())); } void BluetoothInterface::initConnectionsService(QLowEnergyService *vService) { if ( ! vService ) return; connect(vService, SIGNAL( characteristicChanged (const QLowEnergyCharacteristic &, const QByteArray &)), this , SLOT(onServiceCharacteristicChanged (const QLowEnergyCharacteristic &, const QByteArray &))); connect(vService, SIGNAL( characteristicChanged (const QLowEnergyCharacteristic &, const QByteArray &)), this , SLOT(onServiceCharacteristicChanged (const QLowEnergyCharacteristic &, const QByteArray &))); connect(vService, SIGNAL( characteristicRead (const QLowEnergyCharacteristic &, const QByteArray &)), this , SLOT(onServiceCharacteristicRead (const QLowEnergyCharacteristic &, const QByteArray &))); connect(vService, SIGNAL( characteristicWritten (const QLowEnergyCharacteristic &, const QByteArray &)), this , SLOT(onServiceCharacteristicWritten (const QLowEnergyCharacteristic &, const QByteArray &))); connect(vService, SIGNAL( descriptorRead (const QLowEnergyDescriptor &, const QByteArray &)), this , SLOT(onServiceDescriptorRead (const QLowEnergyDescriptor &, const QByteArray &))); connect(vService, SIGNAL( descriptorWritten (const QLowEnergyDescriptor &, const QByteArray &)), this , SLOT(onServiceDescriptorWritten (const QLowEnergyDescriptor &, const QByteArray &))); connect(vService, SIGNAL( error ( QLowEnergyService::ServiceError )), this , SLOT(onServiceError ( QLowEnergyService::ServiceError ))); connect(vService, SIGNAL( stateChanged ( QLowEnergyService::ServiceState )), this , SLOT(onServiceStateChanged ( QLowEnergyService::ServiceState ))); } void BluetoothInterface::onServiceError(QLowEnergyService::ServiceError vError) { QLowEnergyService *service = reinterpret_cast(sender()); NOTIFY_SERVICE_ERROR } void BluetoothInterface::onServiceStateChanged(QLowEnergyService::ServiceState vState) { QLowEnergyService *service = reinterpret_cast(sender()); qDebug() << "Service State:" << service->serviceUuid() << service->serviceName() << vState; switch (vState) { case QLowEnergyService::InvalidService : NOTIFY_DETAILS_ERROR break; case QLowEnergyService::ServiceDiscovered : NOTIFY_SERVICE_DETAILS_DONE for ( auto const &detail: service->characteristics()) { qDebug() << " ~~~~~ " << detail.name() << detail.uuid() << detail.value(); } if ( service->serviceUuid() == QBluetoothUuid (QBluetoothUuid::BloodPressure)) { // blood pressure measurements const QLowEnergyCharacteristic detailBPMeas = service->characteristic(QBluetoothUuid(QBluetoothUuid::BloodPressureMeasurement)); QLowEnergyDescriptor configBPMeas = detailBPMeas.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); if (configBPMeas.isValid()) service->writeDescriptor(configBPMeas, QByteArray::fromHex("0100")); // blood pressure feature const QLowEnergyCharacteristic detailBPFeat = service->characteristic(QBluetoothUuid(QBluetoothUuid::BloodPressureFeature)); if (!detailBPFeat.isValid()) { qDebug() << "Blood pressure feature not found."; return; } service->readCharacteristic(detailBPFeat); _connectionActive = true; } break; default: break; } } /*! * \brief BluetoothInterface::initDevice * \return Initializes the device by making a new device and initializes its connections */ bool BluetoothInterface::initDevice() { if ( ! makeDevice() ) return false; initConnectionsDevice(); return true; } /*! * \brief BluetoothInterface::makeDevice * \details Makes a device by disconnect and delete the old one and create a new one, and * validates if the device was created successfully, and * notifies the observers (view: VBluetooth) that the state is * MBluetooth::eIS_Device_Init if succeeded, or the state is * MBluetooth::eIS_Device_Error_Init if failed. * \return returns true if the remote Bluetooth device _device is created successfully, false otherwise. */ bool BluetoothInterface::makeDevice() { quitDevice(); _device = QLowEnergyController::createCentral(_temp, this); _temp = QBluetoothDeviceInfo(); if ( _device ) NOTIFY_DEVICE_INIT else { NOTIFY_DEVICE_INIT_ERROR return false; } return true; } /*! * \brief BluetoothInterface::notifyStateChange * \details sends a notification signal to the observers (view: VBluetooth) on any reported state changes. * \param vData - the Bluetooth data model containing information about the current state and device. */ void BluetoothInterface::notifyStateChange(const BluetoothData &vData) { emit didStateChange(vData); } /*! * \brief BluetoothInterface::timerEvent * \details The main QObject's timer which has been set with the interval of the _interval (1s) * The main purpose is to reconnect to the device on each second interval to get any new measurement data. */ void BluetoothInterface::timerEvent(QTimerEvent *) { if ( _connectionActive ) { if ( _device ) _device->connectToDevice(); } } /*! * \brief BluetoothInterface::isDeviceSupported * \param vName - remote device name * \return true if the name provided starts with one of the supported keywords mentioned in the Omron documentation */ bool BluetoothInterface::isDeviceSupported( const QString &vName ) { for (const QString &name : _supportedDeviceKeywords ) if ( vName.startsWith(name) ) return true; return false; } /*! * \brief BluetoothInterface::isLocalValid * \details checks if the local Bluetooth device is valid, and * notifies the observers (view: VBluetooth) that local is not valid if fails. * \return false if the local Bluetooth device _local is not valid */ bool BluetoothInterface::isLocalValid() { if (! _local->isValid() ) { NOTIFY_LOCAL_ERROR_INVALID return false; } return true; } /*! * \brief BluetoothInterface::isInfoValid * \details checks if the device info _temp is valid. * notifies the observers (view: VBluetooth) that the device info is not valid. * \return false if the device info _local is not valid * \note the device info _temp is set to a default QBluetoothDeviceInfo which is invalid by default, * when the interface initialized (onStart), and * when a scan started (ondoScan) */ bool BluetoothInterface::isInfoValid() { if ( ! _temp.isValid() ) { NOTIFY_SCAN_NOTFOUND // not a valid device found return false; } return true; } /*! * \brief BluetoothInterface::isDeviceValid * \details checks if the remote Bluetooth device has been setup. * \return returns false if no device is setup. */ bool BluetoothInterface::isDeviceValid() { if ( _device ) { NOTIFY_DEVICE_INIT } else { NOTIFY_DEVICE_INIT_ERROR return false; } return true; } // bool BluetoothInterface::isServiceValid(QLowEnergyService *vService) // { // if ( vService ) { NOTIFY_SERVICE_INIT } // else { NOTIFY_SERVICE_INIT_ERROR // return false; // } // return true; // } bool BluetoothInterface::isDetailValid(const QLowEnergyCharacteristic &vDetail) { if ( ! vDetail.isValid() ) { NOTIFY_DETAILS_INVALID return false; } return true; } /*! * \brief BluetoothInterface::quitDevice * \details quits and deletes the old remote device _device and the device info _temp. */ void BluetoothInterface::quitDevice() { _connectionActive = false; if ( _device ) { NOTIFY_DEVICE_DONE quitServices(); // it seems safe to delete all the services when the device is deleted because the current discovered service may not apply to the next device. _device->disconnectFromDevice(); delete _device; _device = nullptr; } } /*! * \brief BluetoothInterface::initService * \details */ void BluetoothInterface::initServices(const QBluetoothUuid &vService) { makeServices(vService); } void BluetoothInterface::makeServices(const QBluetoothUuid &vService) { if (vService == QBluetoothUuid (QBluetoothUuid::DeviceInformation )) { _serviceDeviceInformation = _device->createServiceObject(vService, this); initConnectionsService (_serviceDeviceInformation ); return; } if (vService == QBluetoothUuid (QBluetoothUuid::CurrentTimeService )) { _serviceCurrentTime = _device->createServiceObject(vService, this); initConnectionsService (_serviceCurrentTime ); return; } if (vService == QBluetoothUuid (QBluetoothUuid::BloodPressure )) { _serviceBloodPressure = _device->createServiceObject(vService, this); initConnectionsService (_serviceBloodPressure ); return; } if (vService == QBluetoothUuid (QBluetoothUuid::BatteryService )) { _serviceBattery = _device->createServiceObject(vService, this); initConnectionsService (_serviceBattery ); return; } } /*! * \brief BluetoothInterface::quitServices * \details quits and deletes the old remote services. */ void BluetoothInterface::quitServices() { NOTIFY_SERVICE_DONE if ( _serviceDeviceInformation ) { delete _serviceDeviceInformation ; _serviceDeviceInformation = nullptr; } if ( _serviceCurrentTime ) { delete _serviceCurrentTime ; _serviceCurrentTime = nullptr; } if ( _serviceBloodPressure ) { delete _serviceBloodPressure ; _serviceBloodPressure = nullptr; } if ( _serviceBattery ) { delete _serviceBattery ; _serviceBattery = nullptr; } }