1/***************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the QtBluetooth module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "heartrate-global.h"
52#include "devicehandler.h"
53#include "deviceinfo.h"
54#include <QtEndian>
55#include <QRandomGenerator>
56
57DeviceHandler::DeviceHandler(QObject *parent) :
58 BluetoothBaseClass(parent),
59 m_foundHeartRateService(false),
60 m_measuring(false),
61 m_currentValue(0),
62 m_min(0), m_max(0), m_sum(0), m_avg(0), m_calories(0)
63{
64#ifdef SIMULATOR
65 m_demoTimer.setSingleShot(false);
66 m_demoTimer.setInterval(2000);
67 connect(&m_demoTimer, &QTimer::timeout, this, &DeviceHandler::updateDemoHR);
68 m_demoTimer.start();
69 updateDemoHR();
70#endif
71}
72
73void DeviceHandler::setAddressType(AddressType type)
74{
75 switch (type) {
76 case DeviceHandler::AddressType::PublicAddress:
77 m_addressType = QLowEnergyController::PublicAddress;
78 break;
79 case DeviceHandler::AddressType::RandomAddress:
80 m_addressType = QLowEnergyController::RandomAddress;
81 break;
82 }
83}
84
85DeviceHandler::AddressType DeviceHandler::addressType() const
86{
87 if (m_addressType == QLowEnergyController::RandomAddress)
88 return DeviceHandler::AddressType::RandomAddress;
89
90 return DeviceHandler::AddressType::PublicAddress;
91}
92
93void DeviceHandler::setDevice(DeviceInfo *device)
94{
95 clearMessages();
96 m_currentDevice = device;
97
98#ifdef SIMULATOR
99 setInfo(tr("Demo device connected."));
100 return;
101#endif
102
103 // Disconnect and delete old connection
104 if (m_control) {
105 m_control->disconnectFromDevice();
106 delete m_control;
107 m_control = nullptr;
108 }
109
110 // Create new controller and connect it if device available
111 if (m_currentDevice) {
112
113 // Make connections
114 //! [Connect-Signals-1]
115 m_control = QLowEnergyController::createCentral(remoteDevice: m_currentDevice->getDevice(), parent: this);
116 //! [Connect-Signals-1]
117 m_control->setRemoteAddressType(m_addressType);
118 //! [Connect-Signals-2]
119 connect(sender: m_control, signal: &QLowEnergyController::serviceDiscovered,
120 receiver: this, slot: &DeviceHandler::serviceDiscovered);
121 connect(sender: m_control, signal: &QLowEnergyController::discoveryFinished,
122 receiver: this, slot: &DeviceHandler::serviceScanDone);
123
124 connect(sender: m_control, signal: static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
125 context: this, slot: [this](QLowEnergyController::Error error) {
126 Q_UNUSED(error);
127 setError("Cannot connect to remote device.");
128 });
129 connect(sender: m_control, signal: &QLowEnergyController::connected, context: this, slot: [this]() {
130 setInfo("Controller connected. Search services...");
131 m_control->discoverServices();
132 });
133 connect(sender: m_control, signal: &QLowEnergyController::disconnected, context: this, slot: [this]() {
134 setError("LowEnergy controller disconnected");
135 });
136
137 // Connect
138 m_control->connectToDevice();
139 //! [Connect-Signals-2]
140 }
141}
142
143void DeviceHandler::startMeasurement()
144{
145 if (alive()) {
146 m_start = QDateTime::currentDateTime();
147 m_min = 0;
148 m_max = 0;
149 m_avg = 0;
150 m_sum = 0;
151 m_calories = 0;
152 m_measuring = true;
153 m_measurements.clear();
154 emit measuringChanged();
155 }
156}
157
158void DeviceHandler::stopMeasurement()
159{
160 m_measuring = false;
161 emit measuringChanged();
162}
163
164//! [Filter HeartRate service 1]
165void DeviceHandler::serviceDiscovered(const QBluetoothUuid &gatt)
166{
167 if (gatt == QBluetoothUuid(QBluetoothUuid::HeartRate)) {
168 setInfo("Heart Rate service discovered. Waiting for service scan to be done...");
169 m_foundHeartRateService = true;
170 }
171}
172//! [Filter HeartRate service 1]
173
174void DeviceHandler::serviceScanDone()
175{
176 setInfo("Service scan done.");
177
178 // Delete old service if available
179 if (m_service) {
180 delete m_service;
181 m_service = nullptr;
182 }
183
184//! [Filter HeartRate service 2]
185 // If heartRateService found, create new service
186 if (m_foundHeartRateService)
187 m_service = m_control->createServiceObject(service: QBluetoothUuid(QBluetoothUuid::HeartRate), parent: this);
188
189 if (m_service) {
190 connect(sender: m_service, signal: &QLowEnergyService::stateChanged, receiver: this, slot: &DeviceHandler::serviceStateChanged);
191 connect(sender: m_service, signal: &QLowEnergyService::characteristicChanged, receiver: this, slot: &DeviceHandler::updateHeartRateValue);
192 connect(sender: m_service, signal: &QLowEnergyService::descriptorWritten, receiver: this, slot: &DeviceHandler::confirmedDescriptorWrite);
193 m_service->discoverDetails();
194 } else {
195 setError("Heart Rate Service not found.");
196 }
197//! [Filter HeartRate service 2]
198}
199
200// Service functions
201//! [Find HRM characteristic]
202void DeviceHandler::serviceStateChanged(QLowEnergyService::ServiceState s)
203{
204 switch (s) {
205 case QLowEnergyService::DiscoveringServices:
206 setInfo(tr(s: "Discovering services..."));
207 break;
208 case QLowEnergyService::ServiceDiscovered:
209 {
210 setInfo(tr(s: "Service discovered."));
211
212 const QLowEnergyCharacteristic hrChar = m_service->characteristic(uuid: QBluetoothUuid(QBluetoothUuid::HeartRateMeasurement));
213 if (!hrChar.isValid()) {
214 setError("HR Data not found.");
215 break;
216 }
217
218 m_notificationDesc = hrChar.descriptor(uuid: QBluetoothUuid::ClientCharacteristicConfiguration);
219 if (m_notificationDesc.isValid())
220 m_service->writeDescriptor(descriptor: m_notificationDesc, newValue: QByteArray::fromHex(hexEncoded: "0100"));
221
222 break;
223 }
224 default:
225 //nothing for now
226 break;
227 }
228
229 emit aliveChanged();
230}
231//! [Find HRM characteristic]
232
233//! [Reading value]
234void DeviceHandler::updateHeartRateValue(const QLowEnergyCharacteristic &c, const QByteArray &value)
235{
236 // ignore any other characteristic change -> shouldn't really happen though
237 if (c.uuid() != QBluetoothUuid(QBluetoothUuid::HeartRateMeasurement))
238 return;
239
240 auto data = reinterpret_cast<const quint8 *>(value.constData());
241 quint8 flags = *data;
242
243 //Heart Rate
244 int hrvalue = 0;
245 if (flags & 0x1) // HR 16 bit? otherwise 8 bit
246 hrvalue = static_cast<int>(qFromLittleEndian<quint16>(source: data[1]));
247 else
248 hrvalue = static_cast<int>(data[1]);
249
250 addMeasurement(value: hrvalue);
251}
252//! [Reading value]
253
254#ifdef SIMULATOR
255void DeviceHandler::updateDemoHR()
256{
257 int randomValue = 0;
258 if (m_currentValue < 30) { // Initial value
259 randomValue = 55 + QRandomGenerator::global()->bounded(30);
260 } else if (!m_measuring) { // Value when relax
261 randomValue = qBound(55, m_currentValue - 2 + QRandomGenerator::global()->bounded(5), 75);
262 } else { // Measuring
263 randomValue = m_currentValue + QRandomGenerator::global()->bounded(10) - 2;
264 }
265
266 addMeasurement(randomValue);
267}
268#endif
269
270void DeviceHandler::confirmedDescriptorWrite(const QLowEnergyDescriptor &d, const QByteArray &value)
271{
272 if (d.isValid() && d == m_notificationDesc && value == QByteArray::fromHex(hexEncoded: "0000")) {
273 //disabled notifications -> assume disconnect intent
274 m_control->disconnectFromDevice();
275 delete m_service;
276 m_service = nullptr;
277 }
278}
279
280void DeviceHandler::disconnectService()
281{
282 m_foundHeartRateService = false;
283
284 //disable notifications
285 if (m_notificationDesc.isValid() && m_service
286 && m_notificationDesc.value() == QByteArray::fromHex(hexEncoded: "0100")) {
287 m_service->writeDescriptor(descriptor: m_notificationDesc, newValue: QByteArray::fromHex(hexEncoded: "0000"));
288 } else {
289 if (m_control)
290 m_control->disconnectFromDevice();
291
292 delete m_service;
293 m_service = nullptr;
294 }
295}
296
297bool DeviceHandler::measuring() const
298{
299 return m_measuring;
300}
301
302bool DeviceHandler::alive() const
303{
304#ifdef SIMULATOR
305 return true;
306#endif
307
308 if (m_service)
309 return m_service->state() == QLowEnergyService::ServiceDiscovered;
310
311 return false;
312}
313
314int DeviceHandler::hr() const
315{
316 return m_currentValue;
317}
318
319int DeviceHandler::time() const
320{
321 return m_start.secsTo(m_stop);
322}
323
324int DeviceHandler::maxHR() const
325{
326 return m_max;
327}
328
329int DeviceHandler::minHR() const
330{
331 return m_min;
332}
333
334float DeviceHandler::average() const
335{
336 return m_avg;
337}
338
339float DeviceHandler::calories() const
340{
341 return m_calories;
342}
343
344void DeviceHandler::addMeasurement(int value)
345{
346 m_currentValue = value;
347
348 // If measuring and value is appropriate
349 if (m_measuring && value > 30 && value < 250) {
350
351 m_stop = QDateTime::currentDateTime();
352 m_measurements << value;
353
354 m_min = m_min == 0 ? value : qMin(a: value, b: m_min);
355 m_max = qMax(a: value, b: m_max);
356 m_sum += value;
357 m_avg = (double)m_sum / m_measurements.size();
358 m_calories = ((-55.0969 + (0.6309 * m_avg) + (0.1988 * 94) + (0.2017 * 24)) / 4.184) * 60 * time()/3600;
359 }
360
361 emit statsChanged();
362}
363

source code of qtconnectivity/examples/bluetooth/heartrate-game/devicehandler.cpp