/*************************************************************************** ** ** 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 #include 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(&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(value.constData()); quint8 flags = data[0]; //Heart Rate int hrvalue = 0; if (flags & 0x1) // HR 16 bit? otherwise 8 bit hrvalue = (int)qFromLittleEndian(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 + QRandomGenerator::global()->bounded(30); } else if (!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); } #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(); }