2017-02-28 14:30:46 +00:00
|
|
|
/***************************************************************************
|
|
|
|
**
|
|
|
|
** 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$
|
|
|
|
**
|
|
|
|
****************************************************************************/
|
|
|
|
|
2017-04-03 10:17:20 +00:00
|
|
|
#include "heartrate-global.h"
|
2017-02-28 14:30:46 +00:00
|
|
|
#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)
|
|
|
|
{
|
2017-04-03 10:17:20 +00:00
|
|
|
#ifdef SIMULATOR
|
2017-02-28 14:30:46 +00:00
|
|
|
m_demoTimer.setSingleShot(false);
|
|
|
|
m_demoTimer.setInterval(2000);
|
|
|
|
connect(&m_demoTimer, &QTimer::timeout, this, &DeviceHandler::updateDemoHR);
|
|
|
|
m_demoTimer.start();
|
|
|
|
updateDemoHR();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2017-03-15 12:19:46 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-02-28 14:30:46 +00:00
|
|
|
void DeviceHandler::setDevice(DeviceInfo *device)
|
|
|
|
{
|
|
|
|
clearMessages();
|
|
|
|
m_currentDevice = device;
|
|
|
|
|
2017-04-03 10:17:20 +00:00
|
|
|
#ifdef SIMULATOR
|
2017-02-28 14:30:46 +00:00
|
|
|
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);
|
2017-03-15 12:19:46 +00:00
|
|
|
m_control->setRemoteAddressType(m_addressType);
|
2017-02-28 14:30:46 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-04-03 10:17:20 +00:00
|
|
|
#ifdef SIMULATOR
|
2017-02-28 14:30:46 +00:00
|
|
|
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)
|
|
|
|
{
|
2017-04-03 14:24:25 +00:00
|
|
|
if (d.isValid() && d == m_notificationDesc && value == QByteArray::fromHex("0000")) {
|
2017-02-28 14:30:46 +00:00
|
|
|
//disabled notifications -> assume disconnect intent
|
|
|
|
m_control->disconnectFromDevice();
|
|
|
|
delete m_service;
|
|
|
|
m_service = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DeviceHandler::disconnectService()
|
|
|
|
{
|
|
|
|
m_foundHeartRateService = false;
|
|
|
|
|
|
|
|
//disable notifications
|
2017-04-03 14:24:25 +00:00
|
|
|
if (m_notificationDesc.isValid() && m_service
|
|
|
|
&& m_notificationDesc.value() == QByteArray::fromHex("0100")) {
|
2017-02-28 14:30:46 +00:00
|
|
|
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
|
|
|
|
{
|
2017-04-03 10:17:20 +00:00
|
|
|
#ifdef SIMULATOR
|
2017-02-28 14:30:46 +00:00
|
|
|
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();
|
|
|
|
}
|