Index: sources/bluetooth/BluetoothInterface.cpp =================================================================== diff -u -rc9f8f8cf3c6c37fc6460d8675c62c9442c4d4263 -r4a95b249fb87bf69d909452ba374c794b89d423b --- sources/bluetooth/BluetoothInterface.cpp (.../BluetoothInterface.cpp) (revision c9f8f8cf3c6c37fc6460d8675c62c9442c4d4263) +++ sources/bluetooth/BluetoothInterface.cpp (.../BluetoothInterface.cpp) (revision 4a95b249fb87bf69d909452ba374c794b89d423b) @@ -150,6 +150,8 @@ #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()) ); +#define NOTIFY_LOCAL_PAIRING notifyStateChange(MBluetooth(MBluetooth::eIS_Device_Pairing )); + // ~~~~~~~~~~ Scan #define NOTIFY_SCAN_DISCOVER notifyStateChange(MBluetooth(MBluetooth::eIS_Scan_Discover , \ vInfo.address().toString() , \ @@ -194,6 +196,8 @@ #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 )); @@ -290,7 +294,10 @@ this , SLOT(onLocalDeviceDisconnect (QBluetoothAddress ))); connect(_local , SIGNAL( error (QBluetoothLocalDevice::Error )), this , SLOT(onLocalError (QBluetoothLocalDevice::Error ))); + connect(_local , SIGNAL( pairingFinished (QBluetoothAddress, QBluetoothLocalDevice::Pairing)), + this , SLOT(onLocalPairingFinished (QBluetoothAddress, QBluetoothLocalDevice::Pairing))); + if ( ! isAgentValid() ) return; // Agent connections connect(_agent , SIGNAL( deviceDiscovered (QBluetoothDeviceInfo )), @@ -407,15 +414,58 @@ } _local->powerOn(); + _agent = new QBluetoothDeviceDiscoveryAgent (this); quitDevice(); _agent->setLowEnergyDiscoveryTimeout(5000); + initConnections(); NOTIFY_LOCAL_INIT NOTIFY_IDLE + + // initial scan to populate bluetooth device list + startScan(); + + // It is necessary to initialize the pairing agent to assure that the pairing is + // retained after the device disconnects (Similar to how bluetoothctl does it) + initializePairingAgent(); } /*! + * \brief BluetoothInterface::initializePairingAgent + * \details Initialize and register the pairing agent components + */ +void BluetoothInterface::initializePairingAgent() { + // construct the custom bluetooth pairing agent + _pairingAgent = new BluetoothPairingAgent(); + + // construct the QDBusAbstractAdaptor class + _agentAdaptor = new Agent1Adaptor(_pairingAgent); + + // For pairing agent object to be visible on the d-bus, need to call the registerObject method + if (QDBusConnection::systemBus().registerObject(_pairingObjectPath, _pairingAgent)) { + // Get the interface reference for the org.bluez.AgentManager1 + QDBusInterface agentManager("org.bluez", "/org/bluez", "org.bluez.AgentManager1", QDBusConnection::systemBus(), this); + if (!agentManager.isValid()) { + qWarning() << Q_FUNC_INFO << agentManager.lastError().message(); + } else { + QVariant agentPath{ QVariant::fromValue(QDBusObjectPath(_pairingObjectPath)) }; + // Call the RegisterAgent method with the correct arguments, passing DisplayOnly + QDBusMessage msg{ agentManager.call("RegisterAgent", agentPath, "DisplayOnly") }; + if (msg.type() == QDBusMessage::ErrorMessage) + qWarning() << Q_FUNC_INFO << msg.errorMessage(); + else { + msg = agentManager.call("RequestDefaultAgent", agentPath); + if (msg.type() == QDBusMessage::ErrorMessage) + qWarning() << Q_FUNC_INFO << msg.errorMessage(); + } + } + } else { + // TODO need error handling + } +} + +/*! * \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. @@ -436,6 +486,15 @@ */ void BluetoothInterface::onLocalDeviceConnect (const QBluetoothAddress &vAddress ) { NOTIFY_LOCAL_CONNECT + + // This indicates the local/host bluetooth have connected passively to the remote device + _reconnectionActive = true; + + if ( _local->pairingStatus(vAddress) != QBluetoothLocalDevice::Unpaired ) { + if ( isDeviceValid() ) { + _device->connectToDevice(); + } + } } /*! @@ -452,9 +511,14 @@ * \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(); +void BluetoothInterface::onLocalError ( QBluetoothLocalDevice::Error vError ) { + if ( vError == QBluetoothLocalDevice::UnknownError ) { + NOTIFY_LOCAL_ERROR + quitDevice(); + } else { + // Could be a pairing error, continue polling for device connection + _reconnectionActive = true; + } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -471,12 +535,21 @@ 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()); + + bool isPaired = _local->pairingStatus(vInfo.address()) == QBluetoothLocalDevice::Paired; + bool isAuthPaired = _local->pairingStatus(vInfo.address()) == QBluetoothLocalDevice::AuthorizedPaired; + + data.pair = isPaired || isAuthPaired; + + if ( data.pair ) { + requestDevicePairing(data); + } emit didDeviceChange(data); NOTIFY_SCAN_FOUND } @@ -525,10 +598,38 @@ */ void BluetoothInterface::onDeviceSelect(const BluetoothDeviceData &vDevice) { stopScan(); + requestDevicePairing(vDevice); +} + +/*! + * \brief BluetoothInterface::requestDevicePairing + * \details Request the pairing of the local/host bluetooth with the remote device + * \param vDevice - The device data + */ +void BluetoothInterface::requestDevicePairing(const BluetoothDeviceData &vDevice) { + if ( ! isLocalValid() ) return; + + NOTIFY_LOCAL_PAIRING + _temp = QBluetoothDeviceInfo(QBluetoothAddress(vDevice.addr), vDevice.name, QBluetoothDeviceInfo::HealthBloodPressureMonitor); - connectToDevice(); + _isPairingKeyWritten = false; + _local->requestPairing(QBluetoothAddress(vDevice.addr), QBluetoothLocalDevice::AuthorizedPaired); } +/*! + * \brief BluetoothInterface::onLocalPairingFinished + * \details Request the pairing of the local/host bluetooth with the remote device + * \param addr - the address of the remote device + * \param pair - the pairing type + */ +void BluetoothInterface::onLocalPairingFinished(const QBluetoothAddress addr, QBluetoothLocalDevice::Pairing pair) { + Q_UNUSED(addr) + if( pair == QBluetoothLocalDevice::Paired || pair == QBluetoothLocalDevice::AuthorizedPaired) { + connectToDevice(); + } + // Else is the unpaired completion case +} + void BluetoothInterface::onAttributeResponse(const DeviceBluetoothPairedQueryResponseData &vData) { if ( ! vData.mAccepted ) { @@ -567,8 +668,11 @@ * notifies the observers (view: VBluetooth) that the state is MBluetooth::eIS_Device_Connect. */ void BluetoothInterface::onDeviceConnect() { - _reconnectionActive = false; + if ( ! isLocalValid() ) return; + if ( ! isDeviceValid()) return; + NOTIFY_DEVICE_CONNECT + _reconnectionActive = false; discoverServices(); } @@ -580,6 +684,19 @@ */ void BluetoothInterface::onDeviceError(QLowEnergyController::Error vError) { + bool isErrorUnknown = ( vError == QLowEnergyController::UnknownError ); + bool isLocalAndDeviceValid = isLocalValid() && isDeviceValid(); + if ( isErrorUnknown ) { + // When polling to reconnect to the remote device, the application encounters an unknown + // error occasionally when the connection fails, but does not report it as a connection error. + + if ( isLocalAndDeviceValid && ( _local->pairingStatus(_device->remoteAddress()) != QBluetoothLocalDevice::Unpaired) ) { + // Code is opting to set _reconnectionActive to true to allow polling to continue since the device + // is still paired and the error encountered is due to not being able to connect to remote device + _reconnectionActive = true; + } + } + // 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; @@ -639,7 +756,6 @@ { interpretBloodPressure(vValue); } - qDebug() << "---------" << vCharacteristic.name() << vValue; } /*! @@ -652,10 +768,43 @@ */ void BluetoothInterface::onServiceCharacteristicRead (const QLowEnergyCharacteristic &vCharacteristic, const QByteArray &vValue) { QLowEnergyService *service = reinterpret_cast(sender()); + NOTIFY_DETAILS_READ + if ( isVendorSpecificService(service) && isDevicePairingCharacteristicUuid(vCharacteristic) ) { + if ( !_isPairingKeyWritten && (vValue.at(0) == _deviceConfirmKeyWriteCodeSuccess ) ) { + // write the key for pairing retention + writeDevicePairingKey(); + _isPairingKeyWritten = true; + } + if(vValue.at(0) == '\x80') { + // No-op key write responded with a success code + } + } } /*! + * \brief BluetoothInterface::isDevicePairingCharacteristicUuid + * \details Indicate whether the characteristic passed is the device pairing characteristic of the vendor specific service + * \return true if the characteristic is the device pairing/bonding characteristic, false otherwise + * \param vCharacteristic - The characteristic object + */ +bool BluetoothInterface::isDevicePairingCharacteristicUuid (const QLowEnergyCharacteristic &vCharacteristic) { + QString characteristicUuid = vCharacteristic.uuid().toString(); + return (characteristicUuid.compare(_devicePairingBondCharacteristicUuid, Qt::CaseInsensitive) == 0); +} + +/*! + * \brief BluetoothInterface::isVendorSpecificService + * \details Indicate whether the service passed has the uuid of a vendor specific service + * \return true if the service is a vendor specific service, false otherwise + * \param vService - The service object + */ +bool BluetoothInterface::isVendorSpecificService (const QLowEnergyService *vService) { + QString serviceUuid = vService->serviceUuid().toString(); + return (serviceUuid.compare(_vendorSpecificServiceUuid, Qt::CaseInsensitive) == 0); +} + +/*! * \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, @@ -667,6 +816,10 @@ */ void BluetoothInterface::onServiceCharacteristicWritten (const QLowEnergyCharacteristic &vCharacteristic, const QByteArray &vValue) { QLowEnergyService *service = reinterpret_cast(sender()); + if ( isVendorSpecificService(service) ) { + service->readCharacteristic(vCharacteristic); + } + NOTIFY_DETAILS_WRITE } @@ -721,10 +874,15 @@ switch (vState) { case QLowEnergyService::ServiceDiscovered : { NOTIFY_SERVICE_DETAILS_DONE + + if ( isVendorSpecificService(service) && !_isPairingKeyWritten) { + enableKeyProgramming(); + } + switch( service->serviceUuid().toUInt32() ) { case QBluetoothUuid::BloodPressure: enableNotify(); - break; + break; case QBluetoothUuid::DeviceInformation: interpretInformation(); @@ -959,12 +1117,11 @@ // - 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(); + // No Operation needed SKIPPER_RST; break; @@ -1040,6 +1197,13 @@ */ void BluetoothInterface::makeServices(const QBluetoothUuid &vService) { + // creating QBluetoothUuid object for the vendor service uuid string creates + // an ambiguous object, so doing string compare + if (vService.toString().compare(_vendorSpecificServiceUuid) == 0) { + _serviceVendorSpecific = _device->createServiceObject(vService, this); + initConnectionsService (_serviceVendorSpecific ); + return; + } if (vService == QBluetoothUuid (QBluetoothUuid::DeviceInformation )) { _serviceDeviceInformation = _device->createServiceObject(vService, this); initConnectionsService (_serviceDeviceInformation ); @@ -1070,6 +1234,11 @@ void BluetoothInterface::discoverServicesDetails() { if ( ! isValid() ) return; + + if ( _serviceVendorSpecific ) { + NOTIFY_SERVICE_DETAILS (_serviceVendorSpecific ) + _serviceVendorSpecific ->discoverDetails(); + } if ( _serviceDeviceInformation ) { NOTIFY_SERVICE_DETAILS (_serviceDeviceInformation ) _serviceDeviceInformation ->discoverDetails(); @@ -1097,6 +1266,11 @@ */ void BluetoothInterface::quitServices() { NOTIFY_SERVICE_DONE + + if ( _serviceVendorSpecific ) { + delete _serviceVendorSpecific ; + _serviceVendorSpecific = nullptr; + } if ( _serviceDeviceInformation ) { delete _serviceDeviceInformation ; _serviceDeviceInformation = nullptr; @@ -1118,8 +1292,43 @@ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~ Characteristics // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/*! + * \brief BluetoothInterface::enableKeyProgramming + * \details Write the special key to enable key programming mode for the pairing retention + */ +void BluetoothInterface::enableKeyProgramming() { + if ( ! _serviceVendorSpecific ) { + NOTIFY_SERVICE_INVALID + return; + } + // write the special value to the characteristic to enable key programming mode + const QLowEnergyCharacteristic detailBPMeas = _serviceVendorSpecific->characteristic(QBluetoothUuid(_devicePairingBondCharacteristicUuid)); + if ( detailBPMeas.isValid() ) { + _serviceVendorSpecific->writeCharacteristic(detailBPMeas, QByteArray::fromHex(_enableKeyProgrammingString)); + } +} + /*! + * \brief BluetoothInterface::writeDevicePairingKey + * \details Write the pairing key value to the bonding characteristic of the vendor specific service. + * This allows the device to maintain the pairing state with the remote device without the need to re-pair + * after the initial pairing. The device will be able to reconnect and retrieve measurements without pairing first. + */ +void BluetoothInterface::writeDevicePairingKey() { + if ( ! _serviceVendorSpecific ) { + NOTIFY_SERVICE_INVALID + return; + } + + // set the value of the pairing key on the device + const QLowEnergyCharacteristic detailBPMeas = _serviceVendorSpecific->characteristic(QBluetoothUuid(_devicePairingBondCharacteristicUuid)); + if ( detailBPMeas.isValid() ) { + _serviceVendorSpecific->writeCharacteristic(detailBPMeas, QByteArray::fromHex(_devicePairingKeyString)); + } +} + +/*! * \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.