heartrate.cpp Example File

heartlistener/heartrate.cpp
/***************************************************************************
**
** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the examples of the QtBluetooth module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** 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.h"

#include <QtEndian>

HeartRate::HeartRate():
    m_currentDevice(QBluetoothDeviceInfo()), foundHeartRateService(false),
    m_max(0), m_min(0), calories(0), m_control(0), timer(0),
    m_service(0)
{
    m_deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);

    connect(m_deviceDiscoveryAgent, SIGNAL(deviceDiscovered(const QBluetoothDeviceInfo&)),
            this, SLOT(addDevice(const QBluetoothDeviceInfo&)));
    connect(m_deviceDiscoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)),
            this, SLOT(deviceScanError(QBluetoothDeviceDiscoveryAgent::Error)));
    connect(m_deviceDiscoveryAgent, SIGNAL(finished()), this, SLOT(scanFinished()));

    // initialize random seed for demo mode
    qsrand(QTime::currentTime().msec());
}

HeartRate::~HeartRate()
{
    qDeleteAll(m_devices);
    m_devices.clear();
}

void HeartRate::deviceSearch()
{
    qDeleteAll(m_devices);
    m_devices.clear();
    m_deviceDiscoveryAgent->start();
    setMessage("Scanning for devices...");
}

void HeartRate::addDevice(const QBluetoothDeviceInfo &device)
{
    if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
        qWarning() << "Discovered LE Device name: " << device.name() << " Address: "
                   << device.address().toString();
        DeviceInfo *dev = new DeviceInfo(device);
        m_devices.append(dev);
        setMessage("Low Energy device found. Scanning for more...");
    }
    //...
}

void HeartRate::scanFinished()
{
    if (m_devices.size() == 0)
        setMessage("No Low Energy devices found");
    Q_EMIT nameChanged();
}

void HeartRate::deviceScanError(QBluetoothDeviceDiscoveryAgent::Error error)
{
    if (error == QBluetoothDeviceDiscoveryAgent::PoweredOffError)
        setMessage("The Bluetooth adaptor is powered off, power it on before doing discovery.");
    else if (error == QBluetoothDeviceDiscoveryAgent::InputOutputError)
        setMessage("Writing or reading from the device resulted in an error.");
    else
        setMessage("An unknown error has occurred.");
}

void HeartRate::setMessage(QString message)
{
    m_info = message;
    Q_EMIT messageChanged();
}

QString HeartRate::message() const
{
    return m_info;
}

QVariant HeartRate::name()
{
    return QVariant::fromValue(m_devices);
}

void HeartRate::connectToService(const QString &address)
{
    m_measurements.clear();

    bool deviceFound = false;
    for (int i = 0; i < m_devices.size(); i++) {
        if (((DeviceInfo*)m_devices.at(i))->getAddress() == address ) {
            m_currentDevice.setDevice(((DeviceInfo*)m_devices.at(i))->getDevice());
            setMessage("Connecting to device...");
            deviceFound = true;
            break;
        }
    }
    // we are running demo mode
    if (!deviceFound) {
        startDemo();
        return;
    }

    if (m_control) {
        m_control->disconnectFromDevice();
        delete m_control;
        m_control = 0;

    }
    m_control = new QLowEnergyController(m_currentDevice.getDevice(), this);
    connect(m_control, SIGNAL(serviceDiscovered(QBluetoothUuid)),
            this, SLOT(serviceDiscovered(QBluetoothUuid)));
    connect(m_control, SIGNAL(discoveryFinished()),
            this, SLOT(serviceScanDone()));
    connect(m_control, SIGNAL(error(QLowEnergyController::Error)),
            this, SLOT(controllerError(QLowEnergyController::Error)));
    connect(m_control, SIGNAL(connected()),
            this, SLOT(deviceConnected()));
    connect(m_control, SIGNAL(disconnected()),
            this, SLOT(deviceDisconnected()));

    m_control->connectToDevice();
}


void HeartRate::deviceConnected()
{
    m_control->discoverServices();
}

void HeartRate::deviceDisconnected()
{
    setMessage("Heart Rate service disconnected");
    qWarning() << "Remote device disconnected";
}


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

void HeartRate::serviceScanDone()
{
    delete m_service;
    m_service = 0;

    if (foundHeartRateService) {
        setMessage("Connecting to service...");
        m_service = m_control->createServiceObject(
                    QBluetoothUuid(QBluetoothUuid::HeartRate), this);
    }

    if (!m_service) {
        setMessage("Heart Rate Service not found.");
        return;
    }

    connect(m_service, SIGNAL(stateChanged(QLowEnergyService::ServiceState)),
            this, SLOT(serviceStateChanged(QLowEnergyService::ServiceState)));
    connect(m_service, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
            this, SLOT(updateHeartRateValue(QLowEnergyCharacteristic,QByteArray)));
    connect(m_service, SIGNAL(descriptorWritten(QLowEnergyDescriptor,QByteArray)),
            this, SLOT(confirmedDescriptorWrite(QLowEnergyDescriptor,QByteArray)));

    m_service->discoverDetails();
}

void HeartRate::disconnectService()
{
    foundHeartRateService = false;
    m_stop = QDateTime::currentDateTime();

    if (m_devices.isEmpty()) {
        if (timer)
            timer->stop();
        return;
    }

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

void HeartRate::controllerError(QLowEnergyController::Error error)
{
    setMessage("Cannot connect to remote device.");
    qWarning() << "Controller Error:" << error;
}

void HeartRate::serviceStateChanged(QLowEnergyService::ServiceState s)
{
    switch (s) {
    case QLowEnergyService::ServiceDiscovered:
    {
        const QLowEnergyCharacteristic hrChar = m_service->characteristic(
                    QBluetoothUuid(QBluetoothUuid::HeartRateMeasurement));
        if (!hrChar.isValid()) {
            setMessage("HR Data not found.");
            break;
        }

        m_notificationDesc = hrChar.descriptor(
                    QBluetoothUuid::ClientCharacteristicConfiguration);
        if (m_notificationDesc.isValid()) {
            m_service->writeDescriptor(m_notificationDesc, QByteArray::fromHex("0100"));
            setMessage("Measuring");
            m_start = QDateTime::currentDateTime();
        }

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

void HeartRate::serviceError(QLowEnergyService::ServiceError e)
{
    switch (e) {
    case QLowEnergyService::DescriptorWriteError:
        setMessage("Cannot obtain HR notifications");
        break;
    default:
        qWarning() << "HR service error:" << e;
    }
}

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

    const quint8 *data = reinterpret_cast<const quint8 *>(value.constData());
    quint8 flags = data[0];

    //Heart Rate
    if (flags & 0x1) { // HR 16 bit? otherwise 8 bit
        const quint16 heartRate = qFromLittleEndian<quint16>(data[1]);
        //qDebug() << "16 bit HR value:" << heartRate;
        m_measurements.append(heartRate);
    } else {
        const quint8 *heartRate = &data[1];
        m_measurements.append(*heartRate);
        //qDebug() << "8 bit HR value:" << *heartRate;
    }

    //Energy Expended
    if (flags & 0x8) {
        int index = (flags & 0x1) ? 5 : 3;
        const quint16 energy = qFromLittleEndian<quint16>(data[index]);
        qDebug() << "Used Energy:" << energy;
    }

    Q_EMIT hrChanged();
}

void HeartRate::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 = 0;
    }
}

int HeartRate::hR() const
{
    if (m_measurements.isEmpty())
        return 0;
    return m_measurements.last();
}

void HeartRate::obtainResults()
{
    Q_EMIT timeChanged();
    Q_EMIT averageChanged();
    Q_EMIT caloriesChanged();
}

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

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

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

float HeartRate::average()
{
    if (m_measurements.size() == 0) {
        return 0;
    } else {
        m_max = 0;
        m_min = 1000;
        int sum = 0;
        for (int i = 0; i < m_measurements.size(); i++) {
            sum += (int) m_measurements.value(i);
            if (((int)m_measurements.value(i)) > m_max)
                m_max = (int)m_measurements.value(i);
            if (((int)m_measurements.value(i)) < m_min)
                m_min = (int)m_measurements.value(i);
        }
        return sum/m_measurements.size();
    }
}

int HeartRate::measurements(int index) const
{
    if (index > m_measurements.size())
        return 0;
    else
        return (int)m_measurements.value(index);
}

int HeartRate::measurementsSize() const
{
    return m_measurements.size();
}

QString HeartRate::deviceAddress() const
{
    return m_currentDevice.getAddress();
}

float HeartRate::caloriesCalculation()
{
    calories = ((-55.0969 + (0.6309 * average()) + (0.1988 * 94) + (0.2017 * 24)) / 4.184) * 60 * time()/3600 ;
    return calories;
}

int HeartRate::numDevices() const
{
    return m_devices.size();
}

void HeartRate::startDemo()
{
    m_start = QDateTime::currentDateTime();
    if (!timer) {
        timer = new QTimer(this);
        connect(timer, SIGNAL(timeout()), this, SLOT(receiveDemo()));
    }
    timer->start(1000);
    setMessage("This is Demo mode");
}

void HeartRate::receiveDemo()
{
    m_measurements.append(randomPulse());
    Q_EMIT hrChanged();
}

int HeartRate::randomPulse() const
{
    // random number between 50 and 70
    return qrand() % (70 - 50) + 50;
}

© 2017 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.