devicehandler.cpp Example File

heartrate-game/devicehandler.cpp
 /***************************************************************************
 **
 ** Copyright (C) 2017 The Qt Company Ltd.
 ** Contact: https://www.qt.io/licensing/
 **
 ** This file is part of the examples of the QtBluetooth module of the Qt Toolkit.
 **
 ** $QT_BEGIN_LICENSE:BSD$
 ** Commercial License Usage
 ** Licensees holding valid commercial Qt licenses may use this file in
 ** accordance with the commercial license agreement provided with the
 ** Software or, alternatively, in accordance with the terms contained in
 ** a written agreement between you and The Qt Company. For licensing terms
 ** and conditions see https://www.qt.io/terms-conditions. For further
 ** information use the contact form at https://www.qt.io/contact-us.
 **
 ** BSD License Usage
 ** Alternatively, you may use this file under the terms of the BSD license
 ** as follows:
 **
 ** "Redistribution and use in source and binary forms, with or without
 ** modification, are permitted provided that the following conditions are
 ** met:
 **   * Redistributions of source code must retain the above copyright
 **     notice, this list of conditions and the following disclaimer.
 **   * Redistributions in binary form must reproduce the above copyright
 **     notice, this list of conditions and the following disclaimer in
 **     the documentation and/or other materials provided with the
 **     distribution.
 **   * Neither the name of The Qt Company Ltd nor the names of its
 **     contributors may be used to endorse or promote products derived
 **     from this software without specific prior written permission.
 **
 **
 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 **
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/

 #include "heartrate-global.h"
 #include "devicehandler.h"
 #include "deviceinfo.h"
 #include <QtEndian>
 #include <QRandomGenerator>

 DeviceHandler::DeviceHandler(QObject *parent) :
     BluetoothBaseClass(parent),
     m_foundHeartRateService(false),
     m_measuring(false),
     m_currentValue(0),
     m_min(0), m_max(0), m_sum(0), m_avg(0), m_calories(0)
 {
 #ifdef SIMULATOR
     m_demoTimer.setSingleShot(false);
     m_demoTimer.setInterval(2000);
     connect(&m_demoTimer, &QTimer::timeout, this, &DeviceHandler::updateDemoHR);
     m_demoTimer.start();
     updateDemoHR();
 #endif
 }

 void DeviceHandler::setAddressType(AddressType type)
 {
     switch (type) {
     case DeviceHandler::AddressType::PublicAddress:
         m_addressType = QLowEnergyController::PublicAddress;
         break;
     case DeviceHandler::AddressType::RandomAddress:
         m_addressType = QLowEnergyController::RandomAddress;
         break;
     }
 }

 DeviceHandler::AddressType DeviceHandler::addressType() const
 {
     if (m_addressType == QLowEnergyController::RandomAddress)
         return DeviceHandler::AddressType::RandomAddress;

     return DeviceHandler::AddressType::PublicAddress;
 }

 void DeviceHandler::setDevice(DeviceInfo *device)
 {
     clearMessages();
     m_currentDevice = device;

 #ifdef SIMULATOR
     setInfo(tr("Demo device connected."));
     return;
 #endif

     // Disconnect and delete old connection
     if (m_control) {
         m_control->disconnectFromDevice();
         delete m_control;
         m_control = nullptr;
     }

     // Create new controller and connect it if device available
     if (m_currentDevice) {

         // Make connections
         m_control = QLowEnergyController::createCentral(m_currentDevice->getDevice(), this);
         m_control->setRemoteAddressType(m_addressType);
         connect(m_control, &QLowEnergyController::serviceDiscovered,
                 this, &DeviceHandler::serviceDiscovered);
         connect(m_control, &QLowEnergyController::discoveryFinished,
                 this, &DeviceHandler::serviceScanDone);

         connect(m_control, static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
                 this, [this](QLowEnergyController::Error error) {
             Q_UNUSED(error);
             setError("Cannot connect to remote device.");
         });
         connect(m_control, &QLowEnergyController::connected, this, [this]() {
             setInfo("Controller connected. Search services...");
             m_control->discoverServices();
         });
         connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
             setError("LowEnergy controller disconnected");
         });

         // Connect
         m_control->connectToDevice();
     }
 }

 void DeviceHandler::startMeasurement()
 {
     if (alive()) {
         m_start = QDateTime::currentDateTime();
         m_min = 0;
         m_max = 0;
         m_avg = 0;
         m_sum = 0;
         m_calories = 0;
         m_measuring = true;
         m_measurements.clear();
         emit measuringChanged();
     }
 }

 void DeviceHandler::stopMeasurement()
 {
     m_measuring = false;
     emit measuringChanged();
 }

 void DeviceHandler::serviceDiscovered(const QBluetoothUuid &gatt)
 {
     if (gatt == QBluetoothUuid(QBluetoothUuid::HeartRate)) {
         setInfo("Heart Rate service discovered. Waiting for service scan to be done...");
         m_foundHeartRateService = true;
     }
 }

 void DeviceHandler::serviceScanDone()
 {
     setInfo("Service scan done.");

     // Delete old service if available
     if (m_service) {
         delete m_service;
         m_service = nullptr;
     }

     // If heartRateService found, create new service
     if (m_foundHeartRateService)
         m_service = m_control->createServiceObject(QBluetoothUuid(QBluetoothUuid::HeartRate), this);

     if (m_service) {
         connect(m_service, &QLowEnergyService::stateChanged, this, &DeviceHandler::serviceStateChanged);
         connect(m_service, &QLowEnergyService::characteristicChanged, this, &DeviceHandler::updateHeartRateValue);
         connect(m_service, &QLowEnergyService::descriptorWritten, this, &DeviceHandler::confirmedDescriptorWrite);
         m_service->discoverDetails();
     } else {
         setError("Heart Rate Service not found.");
     }
 }

 // Service functions
 void DeviceHandler::serviceStateChanged(QLowEnergyService::ServiceState s)
 {
     switch (s) {
     case QLowEnergyService::DiscoveringServices:
         setInfo(tr("Discovering services..."));
         break;
     case QLowEnergyService::ServiceDiscovered:
     {
         setInfo(tr("Service discovered."));

         const QLowEnergyCharacteristic hrChar = m_service->characteristic(QBluetoothUuid(QBluetoothUuid::HeartRateMeasurement));
         if (!hrChar.isValid()) {
             setError("HR Data not found.");
             break;
         }

         m_notificationDesc = hrChar.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
         if (m_notificationDesc.isValid())
             m_service->writeDescriptor(m_notificationDesc, QByteArray::fromHex("0100"));

         break;
     }
     default:
         //nothing for now
         break;
     }

     emit aliveChanged();
 }

 void DeviceHandler::updateHeartRateValue(const QLowEnergyCharacteristic &c, const QByteArray &value)
 {
     // ignore any other characteristic change -> shouldn't really happen though
     if (c.uuid() != QBluetoothUuid(QBluetoothUuid::HeartRateMeasurement))
         return;

     auto data = reinterpret_cast<const quint8 *>(value.constData());
     quint8 flags = *data;

     //Heart Rate
     int hrvalue = 0;
     if (flags & 0x1) // HR 16 bit? otherwise 8 bit
         hrvalue = static_cast<int>(qFromLittleEndian<quint16>(data[1]));
     else
         hrvalue = static_cast<int>(data[1]);

     addMeasurement(hrvalue);
 }

 #ifdef SIMULATOR
 void DeviceHandler::updateDemoHR()
 {
     int randomValue = 0;
     if (m_currentValue < 30) { // Initial value
         randomValue = 55 + QRandomGenerator::global()->bounded(30);
     } else if (!m_measuring) { // Value when relax
         randomValue = qBound(55, m_currentValue - 2 + QRandomGenerator::global()->bounded(5), 75);
     } else { // Measuring
         randomValue = m_currentValue + QRandomGenerator::global()->bounded(10) - 2;
     }

     addMeasurement(randomValue);
 }
 #endif

 void DeviceHandler::confirmedDescriptorWrite(const QLowEnergyDescriptor &d, const QByteArray &value)
 {
     if (d.isValid() && d == m_notificationDesc && value == QByteArray::fromHex("0000")) {
         //disabled notifications -> assume disconnect intent
         m_control->disconnectFromDevice();
         delete m_service;
         m_service = nullptr;
     }
 }

 void DeviceHandler::disconnectService()
 {
     m_foundHeartRateService = false;

     //disable notifications
     if (m_notificationDesc.isValid() && m_service
             && m_notificationDesc.value() == QByteArray::fromHex("0100")) {
         m_service->writeDescriptor(m_notificationDesc, QByteArray::fromHex("0000"));
     } else {
         if (m_control)
             m_control->disconnectFromDevice();

         delete m_service;
         m_service = nullptr;
     }
 }

 bool DeviceHandler::measuring() const
 {
     return m_measuring;
 }

 bool DeviceHandler::alive() const
 {
 #ifdef SIMULATOR
     return true;
 #endif

     if (m_service)
         return m_service->state() == QLowEnergyService::ServiceDiscovered;

     return false;
 }

 int DeviceHandler::hr() const
 {
     return m_currentValue;
 }

 int DeviceHandler::time() const
 {
     return m_start.secsTo(m_stop);
 }

 int DeviceHandler::maxHR() const
 {
     return m_max;
 }

 int DeviceHandler::minHR() const
 {
     return m_min;
 }

 float DeviceHandler::average() const
 {
     return m_avg;
 }

 float DeviceHandler::calories() const
 {
     return m_calories;
 }

 void DeviceHandler::addMeasurement(int value)
 {
     m_currentValue = value;

     // If measuring and value is appropriate
     if (m_measuring && value > 30 && value < 250) {

         m_stop = QDateTime::currentDateTime();
         m_measurements << value;

         m_min = m_min == 0 ? value : qMin(value, m_min);
         m_max = qMax(value, m_max);
         m_sum += value;
         m_avg = (double)m_sum / m_measurements.size();
         m_calories = ((-55.0969 + (0.6309 * m_avg) + (0.1988 * 94) + (0.2017 * 24)) / 4.184) * 60 * time()/3600;
     }

     emit statsChanged();
 }