Index: sources/bluetooth/BLEScanner.cpp =================================================================== diff -u -r0470ff6f209ff0c5089f8f0849b6da04f60f8f41 -rdca8a36ea292270c4de986c0f0872f1ee1e5f85a --- sources/bluetooth/BLEScanner.cpp (.../BLEScanner.cpp) (revision 0470ff6f209ff0c5089f8f0849b6da04f60f8f41) +++ sources/bluetooth/BLEScanner.cpp (.../BLEScanner.cpp) (revision dca8a36ea292270c4de986c0f0872f1ee1e5f85a) @@ -7,41 +7,40 @@ // Project #include "main.h" -#include "logger.h" +#include "Logger.h" - - +// coco begin validated: Cannot be tested on server, but has been validated manually BLEScanner::BLEScanner(QObject *parent) : QObject(parent) { discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this); - macAddress = "EC:21:E5:F4:BC:C9"; // for testing only + + startTimer(_timerInterval); } -void BLEScanner::initConnections() +void BLEScanner::onInitConnections() { // discovery agent - connect(discoveryAgent, SIGNAL(deviceDiscovered(const QBluetoothDeviceInfo&)), + connect(discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)), this, SLOT(onDeviceDiscovered(const QBluetoothDeviceInfo&))); connect(discoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)), this, SLOT(onDiscoveryAgentError(QBluetoothDeviceDiscoveryAgent::Error))); connect(discoveryAgent, SIGNAL(finished()), this, SLOT(onScanFinished())); - } /*! * \brief BLEScanner::init * \details Initializes the class by setting the connections * \return true on first initialization, false if it has already been initialized */ -bool BLEScanner::init() +bool BLEScanner::doInit() { if (_init) return false; _init = true; - initConnections(); + onInitConnections(); LOG_EVENT("UI," + tr("%1 Initialized").arg(metaObject()->className())); @@ -53,10 +52,9 @@ * \brief BLEScanner::quit * Called when the application is exiting. */ -void BLEScanner::quit() +void BLEScanner::onQuit() { - quitThread(); // verified - + onQuitThread(); // verified } @@ -65,50 +63,109 @@ * \details Moves this object to main thread to be handled by QApplicaiton * It will also be destroyed there. */ -void BLEScanner::quitThread() +void BLEScanner::onQuitThread() { if (!_thread ) return; moveToThread(qApp->thread()); } +/*! + * \brief BLEScanner::toggleBLETimer + * Toggles whether the BLE timer is active or not + * \param enabled - (bool) if true, set BLE timer to active + * if false, BLE timer is set to inactive + */ +void BLEScanner::toggleBLETimer(const bool &active) +{ + if (_timerIsActive == active) + return; + LOG_DEBUG(QString("Toggling BLE timer from active=%1 to active=%2").arg(_timerIsActive).arg(active)); + _timerIsActive = active; +} + /*! - * \brief BLEScanner::setMacAddress - * Sets the BLE mac address to pair with - * \param mac - (QString) The mac address to pair to (e.g. "EC:21:E5:F4:BC:C9") + * \brief BLEScanner::timerEvent + * Called at the end of the timer interval when the + * timer is active + * \param event - (QTimerEvent*) holds the timer event + * information (e.g. timer id) */ -void BLEScanner::setMacAddress(const QString &mac) +void BLEScanner::timerEvent(QTimerEvent *event) { - macAddress = mac; + Q_UNUSED(event); + if (!_timerIsActive) + return; + + if (_retryConnection) { + LOG_DEBUG(QString("BLE Device %1 is Not Connected. Attempting reconnection...").arg(selectedDeviceInfo.address().toString())); + onRetryConnectToDevice(); + } else{ + LOG_DEBUG(QString("BLE Device %1 is Connected. Checking in with device.").arg(selectedDeviceInfo.address().toString())); + onRequestDeviceInformation(); + } } /*! - * \brief BLEScanner::onSelectedDevice + * \brief BLEScanner::doSelectDevice * \param addr - The mac address of the device to connect to */ -void BLEScanner::onSelectedDevice(const QString &addr) +void BLEScanner::doSelectDevice(const QString &addr, const QString &name) { - setMacAddress(addr); - for (const QBluetoothDeviceInfo &deviceInfo : devices) { - if (deviceInfo.address().toString() == addr) { - connectToDevice(deviceInfo); - return; - } + if (selectedDeviceInfo.address().toString() == addr) + return; + + didDisconnectFromDevice(selectedDeviceInfo); + + if (discoveryAgent->isActive()) + { + LOG_DEBUG(QString("Stopping Scan to connect to device addr %1 and name %2").arg(addr).arg(name)); + discoveryAgent->stop(); + LOG_DEBUG(QString("Discovery agent is active? ").arg(discoveryAgent->isActive())); + emit didFinishScan(); } + + selectedDeviceInfo = QBluetoothDeviceInfo(QBluetoothAddress(addr), name, + QBluetoothDeviceInfo::HealthBloodPressureMonitor); + onConnectToDevice(selectedDeviceInfo); + } /*! + * \brief BLEScanner::doReselectDevice + * Called when reselecting a device (e.g. on reboot) + * \param deviceInfo - the device info to connect to + */ +void BLEScanner::doReselectDevice(const QBluetoothDeviceInfo &deviceInfo) +{ + if (!deviceInfo.isValid()) + { + LOG_DEBUG(QString("Cannot reselect device with addr %1 and name %2 as it is not valid.") + .arg(deviceInfo.address().toString()).arg(deviceInfo.name())); + return; + } + LOG_DEBUG(QString("Reselecting device %1").arg(deviceInfo.address().toString())); + selectedDeviceInfo = deviceInfo; + onConnectToDevice(deviceInfo); + updateBLECuffCheckinType(true); +} + +/*! * \brief BLEScanner::onDeviceDiscovered * When a new mac address was discovered * \param deviceInfo - The discovered device's information */ void BLEScanner::onDeviceDiscovered(const QBluetoothDeviceInfo& deviceInfo) { - qDebug() << __FUNCTION__ << deviceInfo.address(); - devices.append(deviceInfo); - emit deviceDiscovered(deviceInfo); + for (const QString &prefix : omronDeviceNamesPrefixes) { + if (deviceInfo.name().toLower().contains(prefix.toLower())) + { + devices.insert(0, deviceInfo); + emit didDiscoverDevice(deviceInfo); + } + } } /*! @@ -119,7 +176,7 @@ void BLEScanner::onDiscoveryAgentError(QBluetoothDeviceDiscoveryAgent::Error error) { qDebug() << __FUNCTION__ << error; - emit scanForDevicesError(error); + emit didReceiveScanForDevicesError(error); } /*! @@ -128,65 +185,111 @@ */ void BLEScanner::onScanFinished() { - emit scanFinished(); + emit didFinishScan(); + if (selectedDeviceInfo.isValid()) + toggleBLETimer(true); } /*! * \brief BLEScanner::scanForDevices * Tells the discovery agent to start scanning for devices */ -void BLEScanner::scanForDevices() +void BLEScanner::doScanForDevices() { + toggleBLETimer(false); discoveryAgent->start(); } /*! + * \brief BLEScanner::setupService + * Creates a new service object and discovers details about the service + * \param uuid - the uuid for the service + * \return (QLowEnergyService*) - the created service + */ +QLowEnergyService* BLEScanner::setupService(const QBluetoothUuid &uuid) +{ + QLowEnergyService *service = lowEnergyController->createServiceObject(uuid, this); + + service->discoverDetails(); + + qDebug() << "Service " << service->serviceName() << "UUID: " << uuid << " state: " << service->state(); + + foreach (const QLowEnergyCharacteristic &c, service->characteristics()) { + qDebug() << "----> Characteristic: " << c.name() + << " uuid: " << c.uuid(); + } + return service; +} + +/*! * \brief BLEScanner::onServiceDiscovered * Called when a new service is discovered on the paired BLE device. * \param uuid - the QBluetoothUuid of the discovered service */ void BLEScanner::onServiceDiscovered(const QBluetoothUuid &uuid) { - - QLowEnergyService* service = lowEnergyController->createServiceObject(uuid, this); - - services.append(service); - if (uuid.toString() == omronUnknownServiceName) { - omronUnknownService = service; - + omronUnknownService = setupService(uuid); } else if (uuid.toString() == omronBloodPressureServiceName) { - omronBloodPressureService = service; + omronBloodPressureService = setupService(uuid); } else if (uuid.toString() == omronCurrentTimeServiceName) { - omronCurrentTimeService = service; + omronCurrentTimeService = setupService(uuid); } else if (uuid.toString() == omronBatteryLevelServiceName) { - omronBatteryLevelService = service; + omronBatteryLevelService = setupService(uuid); } + else if (uuid.toString() == omronDeviceInformationServiceName) { + omronDeviceInformationService = setupService(uuid); + } // Keep in case it is needed for the BP/HR cuffs we haven't tested with yet + /*else { + QLowEnergyService *service = setupService(uuid); + if (service != NULL) + services.append(service); + }*/ +} - service->discoverDetails(); +/*! + * \brief BLEScanner::onRequestDeviceSerialNumber + * Sends a request for the device's serial number + */ +void BLEScanner::onRequestDeviceSerialNumber() +{ + if (omronDeviceInformationService == nullptr) + return; - qDebug() << "Service " << service->serviceName() << "UUID: " << uuid << " state: " << service->state(); - - foreach (const QLowEnergyCharacteristic &c, service->characteristics()) { - qDebug() << "----> Characteristic: " << c.name() - << " uuid: " << c.uuid(); - + // read device serial number + const QLowEnergyCharacteristic c = omronDeviceInformationService->characteristic( + QBluetoothUuid(QBluetoothUuid::SerialNumberString) + ); + if (!c.isValid()) { + qDebug() << "Cannot read device information."; + updateBLECuffCheckinType(true); + return; } + omronDeviceInformationService->readCharacteristic(c); +} - +/*! + * \brief BLEScanner::onRequestDeviceInformation + * Requests BP monitor device information + */ +void BLEScanner::onRequestDeviceInformation() +{ + LOG_DEBUG("Requesting BLE device information"); + onRequestDeviceSerialNumber(); } + /*! * \brief BLEScanner::requestBPMeasurement * Requests BP Measurement data. * Must already be connected to the BLE device * The BLE device must be in the correct mode and it * must support indicate / notify for the blood pressure measurement characteristic (0x2A35). */ -void BLEScanner::requestBPMeasurement() +void BLEScanner::doRequestBPMeasurement() { if (omronBloodPressureService == nullptr) { @@ -221,14 +324,14 @@ * Requests blood pressure measurement data * \param serviceState - the new state of the service */ -void BLEScanner::serviceStateChanged(const QLowEnergyService::ServiceState &serviceState) +void BLEScanner::onServiceStateChanged(const QLowEnergyService::ServiceState &serviceState) { switch (serviceState) { case QLowEnergyService::ServiceDiscovered: { - requestBPMeasurement(); + doRequestBPMeasurement(); break; } case QLowEnergyService::InvalidService: @@ -256,10 +359,7 @@ qDebug() << "Invalid service state"; break; } - } - - } /*! @@ -271,33 +371,30 @@ */ void BLEScanner::onCharacteristicChanged(const QLowEnergyCharacteristic &c, const QByteArray &byteArray) { - qDebug() << "@@@@@@@@ Data Read @@@@@@@@@@: " << c.name() << byteArray; if (c.uuid() != QBluetoothUuid(QBluetoothUuid::BloodPressureMeasurement)) { qDebug() << "Ignoring data read for " << c.uuid(); return; } - - parseMeasurement(byteArray); - + doParseMeasurement(byteArray); } /*! * \brief BLEScanner::parseMeasurement * Parses the BP and Pulse Rate measurement data * \param byteArray - the data to be parsed */ -void BLEScanner::parseMeasurement(const QByteArray &byteArray) +void BLEScanner::doParseMeasurement(const QByteArray &byteArray) { - bp_measurement_t measurement; + BLEMeasurementData measurement; // for debugging /* const char *data = "\x16t\x00M\x00Z\x00\xE4\x07\t\x04\n\x05""8@\x00\x00\x00"; - QByteArray byteArray = QByteArray::fromRawData(data, sizeof(bp_measurement)); + QByteArray byteArray = QByteArray::fromRawData(data, sizeof(BLEMeasurementData)); */ const uint8_t *d = reinterpret_cast(byteArray.constData()); @@ -316,22 +413,21 @@ measurement.measurement_status = d[17]; - qDebug() << "flags: " << measurement.flags; - qDebug() << "systolic: " << measurement.systolic; - qDebug() << "diastolic: " << measurement.diastolic; - qDebug() << "mean arterial pressure: " << measurement.mean_arterial_pressure_value; - qDebug() << "year: " << measurement.year; - qDebug() << "month: " << measurement.month; - qDebug() << "day: " << measurement.day; - qDebug() << "hour: " << measurement.hour; - qDebug() << "minute: " << measurement.minute; - qDebug() << "second: " << measurement.second; - qDebug() << "pulse_rate: " << measurement.pulse_rate; - qDebug() << "user_id: " << measurement.user_id; - qDebug() << "measurement_status: " << measurement.measurement_status; + LOG_DEBUG(QString("flags: %1").arg(measurement.flags )); + LOG_DEBUG(QString("systolic: %1").arg(measurement.systolic )); + LOG_DEBUG(QString("diastolic: %1").arg(measurement.diastolic )); + LOG_DEBUG(QString("mean arterial pressure: %1").arg(measurement.mean_arterial_pressure_value)); + LOG_DEBUG(QString("year: %1").arg(measurement.year )); + LOG_DEBUG(QString("month: %1").arg(measurement.month )); + LOG_DEBUG(QString("day: %1").arg(measurement.day )); + LOG_DEBUG(QString("hour: %1").arg(measurement.hour )); + LOG_DEBUG(QString("minute: %1").arg(measurement.minute )); + LOG_DEBUG(QString("second: %1").arg(measurement.second )); + LOG_DEBUG(QString("pulse_rate: %1").arg(measurement.pulse_rate )); + LOG_DEBUG(QString("user_id: %1").arg(measurement.user_id )); + LOG_DEBUG(QString("measurement_status: %1").arg(measurement.measurement_status )); - emit receivedBPMeasurement(measurement); - + emit didReceiveBPMeasurement(measurement); } /*! @@ -340,9 +436,10 @@ * \param desc - the descriptor that was written to * \param byteArray - data confirming a descriptor disconnect or connection */ -void BLEScanner::confirmedDescriptorWrite(const QLowEnergyDescriptor &desc, const QByteArray &byteArray) +void BLEScanner::onConfirmedDescriptorWrite(const QLowEnergyDescriptor &desc, const QByteArray &byteArray) { qDebug() << "Confirmed descriptor write: " << byteArray; + if (desc.isValid() && desc == desc && byteArray == QByteArray::fromHex("0000")) { //disabled notifications -> assume disconnect qDebug() << "deleting omron blood pressure service"; @@ -351,6 +448,7 @@ omronBloodPressureService = nullptr; } + emit didConnectToDevice(selectedDeviceInfo); } /*! @@ -359,10 +457,29 @@ * \param c - the BLE characteristic that was read * \param byteArray - the data read from the BLE characteristic */ -void BLEScanner::serviceCharacteristicsRead(const QLowEnergyCharacteristic &c,const QByteArray &byteArray) +void BLEScanner::onServiceCharacteristicsRead(const QLowEnergyCharacteristic &c,const QByteArray &byteArray) { qDebug() << __FUNCTION__ << c.name() << " data: " << byteArray; +} +/*! + * \brief BLEScanner::makeServiceConnections + * Makes the necessary connections to interact with the given service + * \param service - the service with connections we need + */ +void BLEScanner::makeServiceConnections(QLowEnergyService *service) +{ + connect(service, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), + this, SLOT(onServiceStateChanged(QLowEnergyService::ServiceState))); + + connect(service, SIGNAL(characteristicChanged(QLowEnergyCharacteristic, QByteArray)), + this, SLOT(onCharacteristicChanged(QLowEnergyCharacteristic,QByteArray))); + + connect(service, SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)), + this, SLOT(onServiceCharacteristicsRead(QLowEnergyCharacteristic,QByteArray))); + + connect(service, SIGNAL(descriptorWritten(QLowEnergyDescriptor, QByteArray)), + this, SLOT(onConfirmedDescriptorWrite(QLowEnergyDescriptor, QByteArray))); } /*! @@ -378,21 +495,30 @@ qDebug() << "##############################"; qDebug() << "##############################"; - foreach (QLowEnergyService* service, services) { +// Keep in case it is needed for the BP/HR cuffs we haven't tested with yet +// foreach (QLowEnergyService *service, services) +// makeServiceConnections(service); - connect(service, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), - this, SLOT(serviceStateChanged(QLowEnergyService::ServiceState))); - - connect(service, SIGNAL(characteristicChanged(QLowEnergyCharacteristic, QByteArray)), - this, SLOT(onCharacteristicChanged(QLowEnergyCharacteristic,QByteArray))); - - connect(service, SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)), - this, SLOT(serviceCharacteristicsRead(QLowEnergyCharacteristic,QByteArray))); - - connect(service, SIGNAL(descriptorWritten(QLowEnergyDescriptor, QByteArray)), - this, SLOT(confirmedDescriptorWrite(QLowEnergyDescriptor, QByteArray))); - } - + if (omronUnknownService != NULL) + makeServiceConnections(omronUnknownService); + else + LOG_DEBUG("Unknown service is null"); + if (omronCurrentTimeService != NULL) + makeServiceConnections(omronCurrentTimeService); + else + LOG_DEBUG("Current time service is null"); + if (omronBatteryLevelService != NULL) + makeServiceConnections(omronBatteryLevelService); + else + LOG_DEBUG("Battery level service is null"); + if (omronBloodPressureService != NULL) + makeServiceConnections(omronBloodPressureService); + else + LOG_DEBUG("Blood Pressure service is null"); + if (omronDeviceInformationService != NULL) + makeServiceConnections(omronDeviceInformationService); + else + LOG_DEBUG("Device Information service is null"); } /*! @@ -402,8 +528,20 @@ */ void BLEScanner::onControllerError(const QLowEnergyController::Error &error) { - qDebug() << __FUNCTION__ << "LE controller error: " << error; + emit didReceiveControllerError(error); +} +/*! + * \brief BLEScanner::updateBLECuffCheckinType + * Toggles the type of check we do with the BLE cuff. + * If already connected, just query it repeatedly every 1Hz. + * Otherwise, start trying to reconnect to it. + * \param retryConnection - if true, retry connection, normal checkin otherwise. + */ +void BLEScanner::updateBLECuffCheckinType(bool retryConnection) +{ + _retryConnection = retryConnection; + toggleBLETimer(true); } /*! @@ -413,10 +551,10 @@ */ void BLEScanner::onDeviceConnected() { - qDebug() << __FUNCTION__; - + LOG_DEBUG("Device Connected"); lowEnergyController->discoverServices(); - + updateBLECuffCheckinType(false); + toggleBLETimer(true); } /*! @@ -425,8 +563,8 @@ */ void BLEScanner::onDeviceDisconnected() { - qDebug() << __FUNCTION__; - + LOG_DEBUG("Device Disconnected."); + emit didDisconnectFromDevice(selectedDeviceInfo); } /*! @@ -436,9 +574,12 @@ * Connects to the desired device * \param deviceInfo - The QBluetoothDeviceInfo object to connect to */ -void BLEScanner::connectToDevice(const QBluetoothDeviceInfo& deviceInfo) +void BLEScanner::onConnectToDevice(const QBluetoothDeviceInfo& deviceInfo) { + LOG_DEBUG(QString("Connecting to (%1, %2)").arg(deviceInfo.name()).arg(deviceInfo.address().toString())); + updateBLECuffCheckinType(true); + lowEnergyController = QLowEnergyController::createCentral(deviceInfo); // low energy controller @@ -457,5 +598,17 @@ connect(lowEnergyController, SIGNAL(disconnected()), this, SLOT(onDeviceDisconnected())); + emit didStartConnectingToDevice(); lowEnergyController->connectToDevice(); } + +/*! + * \brief BLEScanner::onRetryConnectToDevice + * Called when we retry to connect to the specific device + */ +void BLEScanner::onRetryConnectToDevice() +{ + lowEnergyController->connectToDevice(); +} + +// coco end