devicehandler.cpp Example File

heartrate-game/devicehandler.cpp

  /***************************************************************************
  **
  ** Copyright (C) 2017 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-global.h"
  #include "devicehandler.h"
  #include "deviceinfo.h"
  #include <QtEndian>

  DeviceHandler::DeviceHandler(QObject *parent) :
      BluetoothBaseClass(parent),
      m_control(0),
      m_service(0),
      m_currentDevice(0),
      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 = 0;
      }

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

          // Make connections
          m_control = new QLowEnergyController(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 = 0;
      }

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

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

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

      addMeasurement(hrvalue);
  }

  #ifdef SIMULATOR
  void DeviceHandler::updateDemoHR()
  {
      int randomValue = 0;
      if (m_currentValue < 30) { // Initial value
          randomValue = 55 + qrand()%30;
      } else if (!m_measuring) { // Value when relax
          randomValue = qBound(55, m_currentValue - 2 + qrand()%5, 75);
      } else { // Measuring
          randomValue = m_currentValue + qrand()%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 = 0;
      }
  }

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

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