diff options
Diffstat (limited to 'examples/bluetooth/heartrate-game/devicehandler.cpp')
-rw-r--r-- | examples/bluetooth/heartrate-game/devicehandler.cpp | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/examples/bluetooth/heartrate-game/devicehandler.cpp b/examples/bluetooth/heartrate-game/devicehandler.cpp new file mode 100644 index 00000000..dfc514b5 --- /dev/null +++ b/examples/bluetooth/heartrate-game/devicehandler.cpp @@ -0,0 +1,354 @@ +/*************************************************************************** +** +** 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 + //! [Connect-Signals-1] + m_control = new QLowEnergyController(m_currentDevice->getDevice(), this); + //! [Connect-Signals-1] + m_control->setRemoteAddressType(m_addressType); + //! [Connect-Signals-2] + 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(); + //! [Connect-Signals-2] + } +} + +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(); +} + +//! [Filter HeartRate service 1] +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; + } +} +//! [Filter HeartRate service 1] + +void DeviceHandler::serviceScanDone() +{ + setInfo("Service scan done."); + + // Delete old service if available + if (m_service) { + delete m_service; + m_service = 0; + } + +//! [Filter HeartRate service 2] + // 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."); + } +//! [Filter HeartRate service 2] +} + +// Service functions +//! [Find HRM characteristic] +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(); +} +//! [Find HRM characteristic] + +//! [Reading value] +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); +} +//! [Reading value] + +#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(); +} |