/***************************************************************************
**
** 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 connectionif (m_control) {
m_control->disconnectFromDevice();
delete m_control;
m_control = nullptr;
}
// Create new controller and connect it if device availableif (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(constQBluetoothUuid&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 availableif (m_service) {
delete m_service;
m_service = nullptr;
}
// If heartRateService found, create new serviceif (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 functionsvoid DeviceHandler::serviceStateChanged(QLowEnergyService::ServiceState s)
{
switch (s) {
caseQLowEnergyService::DiscoveringServices:
setInfo(tr("Discovering services..."));
break;
caseQLowEnergyService::ServiceDiscovered:
{
setInfo(tr("Service discovered."));
constQLowEnergyCharacteristic 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 nowbreak;
}
emit aliveChanged();
}
void DeviceHandler::updateHeartRateValue(constQLowEnergyCharacteristic&c,constQByteArray&value)
{
// ignore any other characteristic change -> shouldn't really happen thoughif (c.uuid() !=QBluetoothUuid(QBluetoothUuid::HeartRateMeasurement))
return;
auto data =reinterpret_cast<constquint8*>(value.constData());
quint8 flags =*data;
//Heart Rateint 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 SIMULATORvoid DeviceHandler::updateDemoHR()
{
int randomValue =0;
if (m_currentValue <30) { // Initial value
randomValue =55+QRandomGenerator::global()->bounded(30);
} elseif (!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);
}
#endifvoid DeviceHandler::confirmedDescriptorWrite(constQLowEnergyDescriptor&d,constQByteArray&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 notificationsif (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 SIMULATORreturntrue;
#endifif (m_service)
return m_service->state() ==QLowEnergyService::ServiceDiscovered;
returnfalse;
}
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 appropriateif (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();
}