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;
  }