/*! * \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 "ApplicationController.h" #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) { 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 // REMINDER:initConnections(); has been removed from here to the ondoStart, // since the _local, _agent objects are created there. LOG_DEBUG(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() { if ( ! _isValid ) return; if ( _local ) _local ->deleteLater(); if ( _agent ) _agent ->deleteLater(); quitDevice(); // coco begin validated: Application termination is not correctly done in coco!!! // it has been tested and works perfectly fine in normal run. quitThread(); // validated } // coco end /*! * \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 QApplication * 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 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // ~~~~~~~~~~ THREAD SAFE state change notifications.~~~~~~~~~~ // // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // #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, true )); #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_POST notifyStateChange(MBluetooth(MBluetooth::eIS_Local_Error_POST )); #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()) ); // ~~~~~~~~~~ Scan #define NOTIFY_SCAN_DISCOVER notifyStateChange(MBluetooth(MBluetooth::eIS_Scan_Discover , \ vInfo.address().toString() , \ vInfo.name(), 0 , \ _local->pairingStatus(vInfo.address()) , \ 0 , \ vInfo.isValid() , \ vInfo.deviceUuid().toString() )); #define NOTIFY_SCAN_FOUND notifyStateChange(MBluetooth(MBluetooth::eIS_Scan_Found , \ vInfo.address().toString() , \ vInfo.name(), 0 , \ _local->pairingStatus(vInfo.address()) , \ 0 , \ vInfo.isValid() , \ vInfo.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_WAITING notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Waiting , \ _device->remoteAddress().toString() , \ _device->remoteName(), _tempBatt )); #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,0, vError, false )); #define NOTIFY_DEVICE_DISCONNECT notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Disconnect , \ _device->remoteAddress().toString() , \ _device->remoteName() )); // ~~~~~~~~~~ Service #define NOTIFY_SERVICE_START notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Start )); #define NOTIFY_SERVICE_DISCOVER notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Discover )); #define NOTIFY_SERVICE_INVALID notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Invalid )); #define NOTIFY_SERVICE_DETAILS(vS) notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Detail , \ _device->remoteAddress().toString() , \ _device->remoteName(), 0 , \ _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(), 0 , \ _local->pairingStatus(_device->remoteAddress()), vError, 0 , \ service->serviceUuid().toString() , \ service->serviceName() )); #define NOTIFY_SERVICE_DONE notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Done )); // ~~~~~~~~~~ Details/Characteristics #define NOTIFY_DETAILS_ERROR notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Detail_Error , \ _device->remoteAddress().toString() , \ _device->remoteName(), 0 , \ _local->pairingStatus(_device->remoteAddress()), vState, 0 , \ service->serviceUuid().toString() , \ service->serviceName() )); #define NOTIFY_DETAILS_CHANGE notifyStateChange(MBluetooth(MBluetooth::eIS_Detail_Change , \ _device->remoteAddress().toString() , \ _device->remoteName(), 0 , \ _local->pairingStatus(_device->remoteAddress()), 0, 1 , \ service->serviceUuid().toString() , \ service->serviceName() , \ vCharacteristic.uuid().toString() , \ vCharacteristic.name() , \ QString(vValue) )); #define NOTIFY_DETAILS_READ notifyStateChange(MBluetooth(MBluetooth::eIS_Detail_Read , \ _device->remoteAddress().toString() , \ _device->remoteName(), 0 , \ _local->pairingStatus(_device->remoteAddress()), 0, 1 , \ service->serviceUuid().toString() , \ service->serviceName() , \ vCharacteristic.uuid().toString() , \ vCharacteristic.name() , \ QString(vValue) )); #define NOTIFY_DETAILS_WRITE notifyStateChange(MBluetooth(MBluetooth::eIS_Detail_Write , \ _device->remoteAddress().toString() , \ _device->remoteName(), 0 , \ _local->pairingStatus(_device->remoteAddress()), 0, 1 , \ service->serviceUuid().toString() , \ service->serviceName() , \ vCharacteristic.uuid().toString() , \ vCharacteristic.name() , \ QString(vValue) )); #define NOTIFY_CONFIG_READ notifyStateChange(MBluetooth(MBluetooth::eIS_Config_Read , \ _device->remoteAddress().toString() , \ _device->remoteName(), 0 , \ _local->pairingStatus(_device->remoteAddress()), 0, 1 , \ service->serviceUuid().toString() , \ service->serviceName() , \ vDescriptor.uuid().toString() , \ vDescriptor.name() , \ QString(vValue) )); #define NOTIFY_CONFIG_WRITE notifyStateChange(MBluetooth(MBluetooth::eIS_Config_Write , \ _device->remoteAddress().toString() , \ _device->remoteName(), 0 , \ _local->pairingStatus(_device->remoteAddress()), 0, 1 , \ service->serviceUuid().toString() , \ service->serviceName() , \ vDescriptor.uuid().toString() , \ vDescriptor.name() , \ QString(vValue) )); #define NOTIFY_SERVICE_DETAILS_DONE notifyStateChange(MBluetooth(MBluetooth::eIS_Service_Detail_Done , \ _device->remoteAddress().toString() , \ _device->remoteName(), 0 , \ _local->pairingStatus(_device->remoteAddress()), 0, 1 , \ service->serviceUuid().toString() , \ service->serviceName() )); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Initializing the connections // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /*! * \brief BluetoothInterface::initConnections * \details Initializes the required signal/slot connection between this class and other objects, for the Local Bluetooth and Discovery Agent. * to be able to communicate. */ void BluetoothInterface::initConnections() { if ( ! isLocalValid() ) return; // Local connections connect(_local , SIGNAL( deviceConnected (QBluetoothAddress )), this , SLOT(onLocalDeviceConnect (QBluetoothAddress ))); connect(_local , SIGNAL( deviceDisconnected (QBluetoothAddress )), this , SLOT(onLocalDeviceDisconnect (QBluetoothAddress ))); connect(_local , SIGNAL( error (QBluetoothLocalDevice::Error )), this , SLOT(onLocalError (QBluetoothLocalDevice::Error ))); if ( ! isAgentValid() ) return; // Agent connections connect(_agent , SIGNAL( deviceDiscovered (QBluetoothDeviceInfo )), this , SLOT(onAgentDiscoverDevice (QBluetoothDeviceInfo ))); connect(_agent , SIGNAL( error (QBluetoothDeviceDiscoveryAgent::Error )), this , SLOT(onAgentDiscoverError (QBluetoothDeviceDiscoveryAgent::Error ))); connect(_agent , SIGNAL( finished ( )), this , SLOT(onAgentDiscoverFinish ( ))); connect(_agent , SIGNAL( canceled ( )), this , SLOT(onAgentDiscoverCancel ( ))); connect(this , SIGNAL(didDeviceSelect (BluetoothDeviceData )), this , SLOT( onDeviceSelect (BluetoothDeviceData ))); connect(&_ApplicationController, SIGNAL(didAttributeResponse(const DeviceBluetoothPairedQueryResponseData &)), this , SLOT( onAttributeResponse(const DeviceBluetoothPairedQueryResponseData &))); } /*! * \brief BluetoothInterface::initConnectionsDevice * \details sets up the remote Bluetooth device _device connections. */ void BluetoothInterface::initConnectionsDevice() { if ( ! isDeviceValid() ) return; // Device controller // The _device, SIGNAL(stateChanged(QLowEnergyController::ControllerState )) signal is not used, // because it is good to know the state but there are not much things can be done at the moment state changes. 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 ())); } /*! * \brief BluetoothInterface::initConnectionsService * \details The Services connection * \param vService */ 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( 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 ))); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Public interface to initialize and start the scan // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /*! * \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::doNotifyStatePOSTError * \details The public slot for public notification on POST error, * since the POST is done by ApplicationPOST class, * and it needs to notify the BluetoothInterface about the failure. */ void BluetoothInterface::doNotifyStatePOSTError() { NOTIFY_LOCAL_ERROR_POST } /*! * \brief BluetoothInterface::ondoStart * \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::ondoStart() { // check the POST was successful and Bluetooth adapter is up. if ( ! isValid() ) { // POST failed. NOTIFY_LOCAL_ERROR_POST return; } _local = new QBluetoothLocalDevice (this); if ( _local->address().toString() == _invalidLocalAddress ) { _isValid = false; NOTIFY_LOCAL_ERROR_POST quit(); return; } _local->powerOn(); _agent = new QBluetoothDeviceDiscoveryAgent (this); quitDevice(); _agent->setLowEnergyDiscoveryTimeout(5000); initConnections(); 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() { startScan(); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Local Bluetooth 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() ) ) { BluetoothDeviceData data; data.addr = vInfo.address().toString(); data.name = vInfo.name(); data.pair = _local->pairingStatus(vInfo.address()); emit didDeviceChange(data); NOTIFY_SCAN_FOUND } } } /*! * \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; } } /*! * \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 } /*! * \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 } /*! * \brief BluetoothInterface::onDeviceSelect * \details the signal which will be called from a View created in the QML namespace to safely set the current selected device. * \param vDevice */ void BluetoothInterface::onDeviceSelect(const BluetoothDeviceData &vDevice) { stopScan(); _temp = QBluetoothDeviceInfo(QBluetoothAddress(vDevice.addr), vDevice.name, QBluetoothDeviceInfo::HealthBloodPressureMonitor); connectToDevice(); } void BluetoothInterface::onAttributeResponse(const DeviceBluetoothPairedQueryResponseData &vData) { if ( ! vData.mAccepted ) { LOG_DEBUG(vData.mMessage); return; } _tryingrepairActive = true; // Notify the View that it is a new scan so clean up the list, and update screen status. NOTIFY_SCAN_START // create a device info with the given paired device information // to simulate the device discovered slot call QBluetoothDeviceInfo info(QBluetoothAddress(vData.mAddr), vData.mName, 0); info.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration); onAgentDiscoverDevice(info); // since it is only one paired device, the device discovery finish is called to process the device. onAgentDiscoverFinish(); // try to call to the device for the first time to make the device and initialize it. BluetoothDeviceData data; data.addr = vData.mAddr; data.name = vData.mName; onDeviceSelect(data); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Remote Device Slots // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /*! * \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() { _reconnectionActive = false; NOTIFY_DEVICE_CONNECT discoverServices(); } /*! * \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) { // if the device is trying to connect to the previously paired device and the device was not on at this moment it should go to the reconnection state. if ( _tryingrepairActive ) _reconnectionActive = true; // if reconnect is active it should display "Device Connecting ..." and shouldn't display the "Connection Error", // because we know we will always get the error since we are trying to connected to a device which is probably off. if ( ! _reconnectionActive ) NOTIFY_DEVICE_ERROR } /*! * \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 } /*! * \brief BluetoothInterface::onDeviceDiscoverService * \details The slot to be called when the remote Bluetooth device _device is discovering a service. * \param vService - The discovered service */ void BluetoothInterface::onDeviceDiscoverService(const QBluetoothUuid &vService) { initServices(vService); } /*! * \brief BluetoothInterface::onDeviceDiscoverFinish * \details The slot to be called when the remote Bluetooth device _device finishes the current service discovering. */ void BluetoothInterface::onDeviceDiscoverFinish() { NOTIFY_SERVICE_DISCOVER discoverServicesDetails(); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ The Services Slots // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /*! * \brief BluetoothInterface::onServiceCharacteristicChanged * \details If the associated controller object is in the central role, * this signal handler is called when the value of characteristic is changed by an event on the peripheral/device side. * In that case, the signal handler call implies that change notifications must have been activated via the characteristic's * ClientCharacteristicConfiguration descriptor prior to the change event on the peripheral. * \param vCharacteristic - The characteristic object * \param vValue - The characteristic's value */ void BluetoothInterface::onServiceCharacteristicChanged (const QLowEnergyCharacteristic &vCharacteristic, const QByteArray &vValue) { QLowEnergyService *service = reinterpret_cast(sender()); NOTIFY_DETAILS_CHANGE if (vCharacteristic.uuid() == QBluetoothUuid(QBluetoothUuid::BloodPressureMeasurement)) { interpretBloodPressure(vValue); } qDebug() << "---------" << vCharacteristic.name() << vValue; } /*! * \brief BluetoothInterface::onServiceCharacteristicRead * \details This signal handler is called when the read request for characteristic successfully returned its value. * The source signal might be triggered by calling characteristicRead(). * If the read operation is not successful, the error() signal is emitted using the CharacteristicReadError flag. * \param vCharacteristic * \param vValue */ void BluetoothInterface::onServiceCharacteristicRead (const QLowEnergyCharacteristic &vCharacteristic, const QByteArray &vValue) { QLowEnergyService *service = reinterpret_cast(sender()); NOTIFY_DETAILS_READ } /*! * \brief BluetoothInterface::onServiceCharacteristicWritten * \details This signal handler is called when the value of characteristic is successfully changed to new vValue. * The change must have been triggered by calling writeCharacteristic(). If the write operation is not successful, * the error() signal is emitted using the CharacteristicWriteError flag. * The reception of the written signal can be considered as a sign that the target device received the to-be-written value * and reports back the status of write request. * \param vCharacteristic - The characteristic object * \param vValue - The characteristic's value */ void BluetoothInterface::onServiceCharacteristicWritten (const QLowEnergyCharacteristic &vCharacteristic, const QByteArray &vValue) { QLowEnergyService *service = reinterpret_cast(sender()); NOTIFY_DETAILS_WRITE } /*! * \brief BluetoothInterface::onServiceDescriptorRead * \details This signal handler is called when the read request for descriptor successfully returned its value. * The signal might be triggered by calling descriptorRead(). * If the read operation is not successful, the error() signal is emitted using the DescriptorReadError flag. * \param vCharacteristic - The characteristic object * \param vValue - The characteristic's value */ void BluetoothInterface::onServiceDescriptorRead (const QLowEnergyDescriptor &vDescriptor , const QByteArray &vValue) { QLowEnergyService *service = reinterpret_cast(sender()); NOTIFY_CONFIG_READ } /*! * \brief BluetoothInterface::onServiceDescriptorWritten * \details This signal is emitted when the value of descriptor is successfully changed to new vValue. * If the associated controller object is in the central role, the change must have been caused by calling writeDescriptor(). * Otherwise, the signal is the result of a write request or command from a GATT client to the respective descriptor. * \param vCharacteristic - The characteristic object * \param vValue - The characteristic's value */ void BluetoothInterface::onServiceDescriptorWritten (const QLowEnergyDescriptor &vDescriptor , const QByteArray &vValue) { QLowEnergyService *service = reinterpret_cast(sender()); NOTIFY_CONFIG_WRITE if ( vDescriptor.uuid() == QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration) && vValue == _bloodPressureNotifyValue ) { _reconnectionActive = true; } } /*! * \brief BluetoothInterface::onServiceError * \details Returns the current service's last occurred error or NoError. * \param vError - the error code in case of error */ void BluetoothInterface::onServiceError (QLowEnergyService::ServiceError vError) { QLowEnergyService *service = reinterpret_cast(sender()); NOTIFY_SERVICE_ERROR } /*! * \brief BluetoothInterface::onServiceStateChanged * \details This signal is emitted when the service's state changes. * The new vState can also be retrieved via state(). * \param vState - The new service state. */ void BluetoothInterface::onServiceStateChanged (QLowEnergyService::ServiceState vState) { if ( ! sender() ) return; QLowEnergyService *service = reinterpret_cast(sender()); switch (vState) { case QLowEnergyService::ServiceDiscovered : { NOTIFY_SERVICE_DETAILS_DONE switch( service->serviceUuid().toUInt32() ) { case QBluetoothUuid::BloodPressure: enableNotify(); break; case QBluetoothUuid::DeviceInformation: interpretInformation(); break; case QBluetoothUuid::BatteryService : { int index = 0; QByteArray data = service->characteristic(QBluetoothUuid::BatteryLevel).value(); Types::U08 batteryLevel; if (GetValue(data, index, batteryLevel)) { _tempBatt = batteryLevel.value; LOG_DEBUG(tr("BCUFF Battery: %1").arg(_tempBatt)); } break; } default: break; } // DEBUG: printCharacteristics(service); break; } case QLowEnergyService::InvalidService : // After disconnection the services are invalidated by Qt. // NOTIFY_DETAILS_ERROR break; default: break; } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Local Bluetooth Device // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /*! * \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 ( ! _isValid ) return false; if ( ! _local ) return false; if ( ! _local->isValid() ) { NOTIFY_LOCAL_ERROR_INVALID quitDevice(); return false; } return true; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Discovery Agent // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /*! * \brief BluetoothInterface::startScan * \details Stars the agent device scan * \return true if the agent can successfully start the scan. */ bool BluetoothInterface::startScan() { if ( ! isValid() ) return false; // POST failed. if (_agent && _agent->isActive()) { NOTIFY_SCAN_REJECT return false; } NOTIFY_SCAN_START quitDevice(); _agent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); return true; } /*! * \brief BluetoothInterface::isAgentValid * \details Check if the discovery agent is a valid pointer. * \return false if not. */ bool BluetoothInterface::isAgentValid() { if (! _agent ) { 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 (doStart), 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::stopScan * \details Stops the agent device scan * \return true if the agent is currently active and is stopped or false if it was not active. */ bool BluetoothInterface::stopScan() { if (! _agent->isActive()) return false; _agent->stop(); // No NOTIFICATION here. will be send in the corresponding slot of the finish/cancel agent scan. return true; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Remote Device Controller Creation [init/make/valid/supported/connect/reconnect/details(services)/quit] // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /*! * \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::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_ERROR return false; } return true; } /*! * \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::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 ( ! isAgentValid() ) return false; if ( ! isInfoValid () ) return false; if ( ! initDevice () ) return false; NOTIFY_DEVICE_START _device->connectToDevice(); return true; } /*! * \brief BluetoothInterface::reconnectToDevice * \details After the device is paired and notification is set, here each second a device re-connection happens. * If the device cannot be connected by two tries, tries to disconnect and re-connect again. * \return false if the Bluetooth interface is not ready. */ bool BluetoothInterface::reconnectToDevice() { if ( ! _reconnectionActive ) return false; if ( ! isLocalValid() ) return false; if ( ! isAgentValid() ) return false; if ( ! _device ) return false; // SKIPPER_DEF(10) // notify each 10 times. // SKIPPER_TST({ // SKIPPER_TRY; // }) // else { // NOTIFY_DEVICE_WAITING // SKIPPER_RST; // } NOTIFY_DEVICE_WAITING SKIPPER_DEF(2) // lets give the controller 2 try. // the device connection and disconnection is very device state dependent. switch(_device->state()) { case QLowEnergyController::ConnectingState : SKIPPER_TRY; // FALLTHROUGH case QLowEnergyController::DiscoveredState : // - For QLowEnergyController::DiscoveredState : // This is the first time after the device is free and reconnect starts. // And the last state of the device after it is done with services and all the things. // At this moment the device has to disconnect to be able to reconnect and read measurements. // - For QLowEnergyController::ConnectingState : // After some testing figured the connecting state take so long to reconnect. // so to move to Unconnected state if instead of waiting for the connecting state timeout (nowhere int the Qt documentation, cannot find a timeout for connecting state.). SKIPPER_TST(break); _device->disconnectFromDevice(); SKIPPER_RST; break; case QLowEnergyController::UnconnectedState : _device->connectToDevice(); break; case QLowEnergyController::ConnectedState : case QLowEnergyController::DiscoveringState : case QLowEnergyController::ClosingState : case QLowEnergyController::AdvertisingState : break; } return true; } /*! * \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 ( _reconnectionActive ) { reconnectToDevice(); } } /*! * \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() { if ( ! isDeviceValid() ) return; NOTIFY_SERVICE_START _device->discoverServices(); } /*! * \brief BluetoothInterface::quitDevice * \details quits and deletes the old remote device _device and the device info _temp. */ void BluetoothInterface::quitDevice() { _reconnectionActive = 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; } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Services Creation [init/make/details(characteristics)/quit] // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /*! * \brief BluetoothInterface::initService * \details initializes the service by its uuid vService. */ void BluetoothInterface::initServices(const QBluetoothUuid &vService) { makeServices(vService); } /*! * \brief BluetoothInterface::makeServices * \details Make remote device's service handler withe the given service uuid information vService. * \param vService - The service uuid */ 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::discoverServicesDetails * \details After a service discovered its detail needs to be discovered by this function. * \note Note that this function is implemented in regards of a Blood Pressure Cuff Device and does not support any other device. */ void BluetoothInterface::discoverServicesDetails() { if ( ! isValid() ) return; 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::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; } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Characteristics // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /*! * \brief BluetoothInterface::enableNotify * \details Enabling the Blood Pressure measurement notification. * This has to be done to first get the OK on the Bluetooth Cuff device. * Second it has to be set to let the Bluetooth Cuff know to notify the Local Device about the Blood Pressure service's characteristics change. */ void BluetoothInterface::enableNotify() { if ( ! _serviceBloodPressure ) { NOTIFY_SERVICE_INVALID return; } // blood pressure measurements const QLowEnergyCharacteristic detailBPMeas = _serviceBloodPressure->characteristic(QBluetoothUuid(QBluetoothUuid::BloodPressureMeasurement)); QLowEnergyDescriptor configBPMeas = detailBPMeas.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); if (configBPMeas.isValid()) _serviceBloodPressure->writeDescriptor(configBPMeas, _bloodPressureNotifyValue); } /*! * \brief BluetoothInterface::notifyBloodPressure * \details The main notifier of the Vital measurement. * \param vModel - The Blood pressure model of type MUIBloodPressure */ void BluetoothInterface::notifyBloodPressure(const MUIBloodPressure &vModel) { UIBloodPressureData data = vModel.data(); emit didActionReceive(data); LOG_EVENT(vModel.toString()); } /*! * \brief BluetoothInterface::mimic * \details A mimic function to simulate the vital measured values. * \param vSystolic - Systolic Viral value * \param vDiastolic - Diastolic Viral value * \param vPulseRate - Pulse Rate Viral value */ void BluetoothInterface::mimic( quint16 vSystolic , quint16 vDiastolic , quint16 vPulseRate ) { MUIBloodPressure model(vSystolic , vDiastolic , vPulseRate ); notifyBloodPressure(model); } /*! * \brief BluetoothInterface::interpretBloodPressure * \details Interpreting the blood pressure message QByteArray vData and extracting the values of Systolic, Diastolic, HeartRate. * \param vData - the received QByteArray of data. */ void BluetoothInterface::interpretBloodPressure(const QByteArray &vData) { MUIBloodPressure model; model.fromByteArray(vData); notifyBloodPressure(model); } /*! * \brief BluetoothInterface::interpretInformation * \details Interpreting the Remote device information QByteArray vData, currently just consoles out. */ void BluetoothInterface::interpretInformation() { for ( auto const &detail: _serviceDeviceInformation->characteristics()) { qDebug() << " ~~~~~ " << detail.name() << detail.uuid() << detail.value(); } } /*! * \brief BluetoothInterface::printCharacteristics * \details Printing out to the console all the service(vService)'s characteristics' name and current value. * \param vService */ void BluetoothInterface::printCharacteristics(QLowEnergyService *vService) { for ( auto const &detail: vService->characteristics()) { qDebug() << " ~~~~~ " << detail.name() << detail.uuid() << detail.value(); } } /*! * \brief BluetoothInterface::requestMeasurements * \details Currently not used, kept for later. */ void BluetoothInterface::requestMeasurements() { if ( ! _serviceBloodPressure ) { NOTIFY_SERVICE_INVALID return; } // blood pressure feature const QLowEnergyCharacteristic detailBPFeat = _serviceBloodPressure->characteristic(QBluetoothUuid(QBluetoothUuid::BloodPressureFeature)); if (!detailBPFeat.isValid()) { qDebug() << "Blood pressure feature not found."; return; } _serviceBloodPressure->readCharacteristic(detailBPFeat); } /*! * \brief BluetoothInterface::requestInformation * \details Currently not used, kept for later. */ void BluetoothInterface::requestInformation() { if ( ! _serviceDeviceInformation ) { NOTIFY_SERVICE_INVALID return; } printCharacteristics(_serviceDeviceInformation); } /*! * \brief BluetoothInterface::requestBattery * \details Currently not used, kept for later. */ void BluetoothInterface::requestBattery() { if ( ! _serviceBattery ) { NOTIFY_SERVICE_INVALID return; } // battery level percent const QLowEnergyCharacteristic detail = _serviceBloodPressure->characteristic(QBluetoothUuid(QBluetoothUuid::BatteryLevel)); if (!detail.isValid()) { qDebug() << "Battery level not found."; return; } _serviceBloodPressure->readCharacteristic(detail); }