1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtBluetooth module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30
31#include <private/qtbluetoothglobal_p.h>
32#if QT_CONFIG(bluez)
33#include <QtBluetooth/private/bluez5_helper_p.h>
34#endif
35#include <QBluetoothAddress>
36#include <QBluetoothLocalDevice>
37#include <QBluetoothDeviceDiscoveryAgent>
38#include <QBluetoothUuid>
39#include <QLowEnergyController>
40#include <QLowEnergyCharacteristic>
41
42#include <QDebug>
43
44/*!
45 This test requires a TI sensor tag with Firmware version: 1.5 (Oct 23 2013).
46 Since revision updates change user strings and even shift handles around
47 other versions than the above are unlikely to succeed. Please update the
48 sensor tag before continuing.
49
50 The TI sensor can be updated using the related iOS app. The Android version
51 doesn't seem to update at this point in time.
52 */
53
54QT_USE_NAMESPACE
55
56// This define must be set if the platform provides access to GATT handles
57// otherwise it must not be defined. As of now the two supported platforms
58// (Android and Bluez/Linux) provide access or some notion of it.
59#ifndef Q_OS_MAC
60#define HANDLES_PROVIDED_BY_PLATFORM
61#endif
62
63#ifdef HANDLES_PROVIDED_BY_PLATFORM
64#define HANDLE_COMPARE(actual,expected) \
65 if (!isBluezDbusLE) {\
66 QCOMPARE(actual, expected);\
67 };
68#else
69#define HANDLE_COMPARE(actual,expected)
70#endif
71
72class tst_QLowEnergyController : public QObject
73{
74 Q_OBJECT
75
76public:
77 tst_QLowEnergyController();
78 ~tst_QLowEnergyController();
79
80private slots:
81 void initTestCase();
82 void init();
83 void cleanupTestCase();
84 void tst_emptyCtor();
85 void tst_connect();
86 void tst_concurrentDiscovery();
87 void tst_defaultBehavior();
88 void tst_writeCharacteristic();
89 void tst_writeCharacteristicNoResponse();
90 void tst_readWriteDescriptor();
91 void tst_customProgrammableDevice();
92 void tst_errorCases();
93private:
94 void verifyServiceProperties(const QLowEnergyService *info);
95 bool verifyClientCharacteristicValue(const QByteArray& value);
96
97 QBluetoothDeviceDiscoveryAgent *devAgent;
98 QBluetoothAddress remoteDevice;
99 QBluetoothDeviceInfo remoteDeviceInfo;
100 QList<QBluetoothUuid> foundServices;
101 bool isBluezDbusLE = false;
102};
103
104tst_QLowEnergyController::tst_QLowEnergyController()
105{
106 //QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
107#ifndef Q_OS_MAC
108 // Core Bluetooth (OS X and iOS) does not work with addresses,
109 // making the code below useless.
110 const QString remote = qgetenv(varName: "BT_TEST_DEVICE");
111 if (!remote.isEmpty()) {
112 remoteDevice = QBluetoothAddress(remote);
113 qWarning() << "Using remote device " << remote << " for testing. Ensure that the device is discoverable for pairing requests";
114 } else {
115 qWarning() << "Not using any remote device for testing. Set BT_TEST_DEVICE env to run manual tests involving a remote device";
116 }
117#endif
118
119#if QT_CONFIG(bluez)
120 // This debug is needed to determine runtime configuration in the Qt CI.
121 isBluezDbusLE = (bluetoothdVersion() >= QVersionNumber(5, 42));
122 qDebug() << "isDBusBluez:" << isBluezDbusLE;
123#endif
124}
125
126tst_QLowEnergyController::~tst_QLowEnergyController()
127{
128
129}
130
131void tst_QLowEnergyController::initTestCase()
132{
133#if !defined(Q_OS_MAC)
134 if (remoteDevice.isNull()
135#if !QT_CONFIG(winrt_bt)
136 || QBluetoothLocalDevice::allDevices().isEmpty()) {
137#else
138 ) {
139#endif
140 qWarning(msg: "No remote device or local adapter found.");
141 return;
142 }
143#elif defined(Q_OS_OSX)
144 // allDevices is always empty on iOS:
145 if (QBluetoothLocalDevice::allDevices().isEmpty()) {
146 qWarning("No local adapter found.");
147 return;
148 }
149#endif
150
151 // QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
152
153 devAgent = new QBluetoothDeviceDiscoveryAgent(this);
154 devAgent->setLowEnergyDiscoveryTimeout(5000);
155
156 QSignalSpy finishedSpy(devAgent, SIGNAL(finished()));
157 // there should be no changes yet
158 QVERIFY(finishedSpy.isValid());
159 QVERIFY(finishedSpy.isEmpty());
160
161 bool deviceFound = false;
162 devAgent->start(method: QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
163 QTRY_VERIFY_WITH_TIMEOUT(finishedSpy.count() > 0, 30000);
164 const QList<QBluetoothDeviceInfo> infos = devAgent->discoveredDevices();
165 for (const QBluetoothDeviceInfo &info : infos) {
166#ifndef Q_OS_MAC
167 if (info.address() == remoteDevice) {
168#else
169 // On OS X/iOS the only way to find the device we are
170 // interested in - is to use device's name.
171 if (info.name().contains("Sensor") && info.name().contains("Tag")) {
172#endif
173 remoteDeviceInfo = info;
174 deviceFound = true;
175 break;
176 }
177 }
178
179 if (!deviceFound)
180 qWarning() << "Unable to find the TI sensor tag device, will skip most of the test";
181
182 // These are the services exported by the TI SensorTag
183#ifndef Q_OS_MAC
184 // Core Bluetooth somehow ignores/hides/fails to discover these services.
185 if (!isBluezDbusLE) // Bluez LE Dbus intentionally hides 0x1800 service
186 foundServices << QBluetoothUuid(QString("00001800-0000-1000-8000-00805f9b34fb"));
187 foundServices << QBluetoothUuid(QString("00001801-0000-1000-8000-00805f9b34fb"));
188#endif
189 foundServices << QBluetoothUuid(QString("0000180a-0000-1000-8000-00805f9b34fb"));
190 foundServices << QBluetoothUuid(QString("0000ffe0-0000-1000-8000-00805f9b34fb"));
191 foundServices << QBluetoothUuid(QString("f000aa00-0451-4000-b000-000000000000"));
192 foundServices << QBluetoothUuid(QString("f000aa10-0451-4000-b000-000000000000"));
193 foundServices << QBluetoothUuid(QString("f000aa20-0451-4000-b000-000000000000"));
194 foundServices << QBluetoothUuid(QString("f000aa30-0451-4000-b000-000000000000"));
195 foundServices << QBluetoothUuid(QString("f000aa40-0451-4000-b000-000000000000"));
196 foundServices << QBluetoothUuid(QString("f000aa50-0451-4000-b000-000000000000"));
197 foundServices << QBluetoothUuid(QString("f000aa60-0451-4000-b000-000000000000"));
198 foundServices << QBluetoothUuid(QString("f000ccc0-0451-4000-b000-000000000000"));
199 foundServices << QBluetoothUuid(QString("f000ffc0-0451-4000-b000-000000000000"));
200}
201
202/*
203 * Executed in between each test function call.
204 */
205void tst_QLowEnergyController::init()
206{
207#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_TVOS)
208 /*
209 * Add a delay to give Android/iOS stack time to catch up in between
210 * the multiple connect/disconnects within each test function.
211 */
212 QTest::qWait(2000);
213#endif
214}
215
216void tst_QLowEnergyController::cleanupTestCase()
217{
218
219}
220
221void tst_QLowEnergyController::tst_emptyCtor()
222{
223 {
224 QBluetoothAddress remoteAddress;
225 QLowEnergyController control(remoteAddress);
226 QSignalSpy connectedSpy(&control, SIGNAL(connected()));
227 QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
228 QSignalSpy errorSpy(&control, SIGNAL(error(QLowEnergyController::Error)));
229 QCOMPARE(control.error(), QLowEnergyController::NoError);
230 control.connectToDevice();
231
232 QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 10000);
233
234 QVERIFY(connectedSpy.isEmpty());
235 QVERIFY(stateSpy.isEmpty());
236
237 QLowEnergyController::Error lastError = errorSpy[0].at(i: 0).value<QLowEnergyController::Error>();
238 QVERIFY(lastError == QLowEnergyController::UnknownRemoteDeviceError
239 || lastError == QLowEnergyController::InvalidBluetoothAdapterError);
240 }
241
242 {
243 QBluetoothDeviceInfo deviceInfo;
244 QLowEnergyController control(deviceInfo);
245 QSignalSpy connectedSpy(&control, SIGNAL(connected()));
246 QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
247 QSignalSpy errorSpy(&control, SIGNAL(error(QLowEnergyController::Error)));
248 QCOMPARE(control.error(), QLowEnergyController::NoError);
249 control.connectToDevice();
250
251 QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 10000);
252
253 QVERIFY(connectedSpy.isEmpty());
254 QVERIFY(stateSpy.isEmpty());
255
256 QLowEnergyController::Error lastError = errorSpy[0].at(i: 0).value<QLowEnergyController::Error>();
257 QVERIFY(lastError == QLowEnergyController::UnknownRemoteDeviceError // if local device on platform found
258 || lastError == QLowEnergyController::InvalidBluetoothAdapterError); // otherwise, e.g. fallback backend
259 }
260
261}
262
263void tst_QLowEnergyController::tst_connect()
264{
265 QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
266
267#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) || QT_CONFIG(winrt_bt)
268 if (!remoteDeviceInfo.isValid())
269#else
270 if (localAdapters.isEmpty() || !remoteDeviceInfo.isValid())
271#endif
272 QSKIP("No local Bluetooth or remote BTLE device found. Skipping test.");
273
274 QLowEnergyController control(remoteDeviceInfo);
275 QCOMPARE(remoteDeviceInfo.deviceUuid(), control.remoteDeviceUuid());
276 QCOMPARE(control.role(), QLowEnergyController::CentralRole);
277 QSignalSpy connectedSpy(&control, SIGNAL(connected()));
278 QSignalSpy disconnectedSpy(&control, SIGNAL(disconnected()));
279 if (remoteDeviceInfo.name().isEmpty())
280 QVERIFY(control.remoteName().isEmpty());
281 else
282 QCOMPARE(control.remoteName(), remoteDeviceInfo.name());
283
284#if !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !QT_CONFIG(winrt_bt)
285 const QBluetoothAddress localAdapter = localAdapters.at(i: 0).address();
286 QCOMPARE(control.localAddress(), localAdapter);
287 QVERIFY(!control.localAddress().isNull());
288#endif
289#ifndef Q_OS_MAC
290 QCOMPARE(control.remoteAddress(), remoteDevice);
291#endif
292 QCOMPARE(control.state(), QLowEnergyController::UnconnectedState);
293 QCOMPARE(control.error(), QLowEnergyController::NoError);
294 QVERIFY(control.errorString().isEmpty());
295 QCOMPARE(disconnectedSpy.count(), 0);
296 QCOMPARE(connectedSpy.count(), 0);
297 QVERIFY(control.services().isEmpty());
298
299 bool wasError = false;
300 control.connectToDevice();
301 QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
302 10000);
303
304 QCOMPARE(disconnectedSpy.count(), 0);
305 if (control.error() != QLowEnergyController::NoError) {
306 //error during connect
307 QCOMPARE(connectedSpy.count(), 0);
308 QCOMPARE(control.state(), QLowEnergyController::UnconnectedState);
309 wasError = true;
310 } else if (control.state() == QLowEnergyController::ConnectingState) {
311 //timeout
312 QCOMPARE(connectedSpy.count(), 0);
313 QVERIFY(control.errorString().isEmpty());
314 QCOMPARE(control.error(), QLowEnergyController::NoError);
315 QVERIFY(control.services().isEmpty());
316 QSKIP("Connection to LE device cannot be established. Skipping test.");
317 return;
318 } else {
319 QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
320 QCOMPARE(connectedSpy.count(), 1);
321 QCOMPARE(control.error(), QLowEnergyController::NoError);
322 QVERIFY(control.errorString().isEmpty());
323 }
324
325 QVERIFY(control.services().isEmpty());
326
327 QList<QLowEnergyService *> savedReferences;
328
329 if (!wasError) {
330 QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
331 QSignalSpy serviceFoundSpy(&control, SIGNAL(serviceDiscovered(QBluetoothUuid)));
332 QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
333 control.discoverServices();
334 QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
335 QCOMPARE(stateSpy.count(), 2);
336 QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
337 QLowEnergyController::DiscoveringState);
338 QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
339 QLowEnergyController::DiscoveredState);
340
341 QVERIFY(!serviceFoundSpy.isEmpty());
342 QVERIFY(serviceFoundSpy.count() >= foundServices.count());
343 QVERIFY(!serviceFoundSpy.isEmpty());
344 QList<QBluetoothUuid> listing;
345 for (int i = 0; i < serviceFoundSpy.count(); i++) {
346 const QVariant v = serviceFoundSpy[i].at(i: 0);
347 listing.append(t: v.value<QBluetoothUuid>());
348 }
349
350 for (const QBluetoothUuid &uuid : qAsConst(t&: foundServices)) {
351 QVERIFY2(listing.contains(uuid),
352 uuid.toString().toLatin1());
353
354 QLowEnergyService *service = control.createServiceObject(service: uuid);
355 QVERIFY2(service, uuid.toString().toLatin1());
356 savedReferences.append(t: service);
357 QCOMPARE(service->type(), QLowEnergyService::PrimaryService);
358 QCOMPARE(service->state(), QLowEnergyService::DiscoveryRequired);
359 }
360
361 // unrelated uuids don't return valid service object
362 // invalid service uuid
363 QVERIFY(!control.createServiceObject(QBluetoothUuid()));
364 // some random uuid
365 QVERIFY(!control.createServiceObject(QBluetoothUuid(QBluetoothUuid::DeviceName)));
366
367 // initiate characteristic discovery
368 for (QLowEnergyService *service : qAsConst(t&: savedReferences)) {
369 qDebug() << "Discovering" << service->serviceUuid();
370 QSignalSpy stateSpy(service,
371 SIGNAL(stateChanged(QLowEnergyService::ServiceState)));
372 QSignalSpy errorSpy(service, SIGNAL(error(QLowEnergyService::ServiceError)));
373 service->discoverDetails();
374
375 QTRY_VERIFY_WITH_TIMEOUT(
376 service->state() == QLowEnergyService::ServiceDiscovered, 10000);
377
378 QCOMPARE(errorSpy.count(), 0); //no error
379 QCOMPARE(stateSpy.count(), 2); //
380
381 verifyServiceProperties(info: service);
382 }
383
384 // ensure that related service objects share same state
385 for (QLowEnergyService* originalService : qAsConst(t&: savedReferences)) {
386 QLowEnergyService *newService = control.createServiceObject(
387 service: originalService->serviceUuid());
388 QVERIFY(newService);
389 QCOMPARE(newService->state(), QLowEnergyService::ServiceDiscovered);
390 delete newService;
391 }
392 }
393
394 // Finish off
395 control.disconnectFromDevice();
396 QTRY_VERIFY_WITH_TIMEOUT(
397 control.state() == QLowEnergyController::UnconnectedState,
398 10000);
399
400 if (wasError) {
401 QCOMPARE(disconnectedSpy.count(), 0);
402 } else {
403 QCOMPARE(disconnectedSpy.count(), 1);
404 // after disconnect all service references must be invalid
405 for (const QLowEnergyService *entry : qAsConst(t&: savedReferences)) {
406 const QBluetoothUuid &uuid = entry->serviceUuid();
407 QVERIFY2(entry->state() == QLowEnergyService::InvalidService,
408 uuid.toString().toLatin1());
409
410 //after disconnect all related characteristics and descriptors are invalid
411 QList<QLowEnergyCharacteristic> chars = entry->characteristics();
412 for (int i = 0; i < chars.count(); i++) {
413 QCOMPARE(chars.at(i).isValid(), false);
414 QList<QLowEnergyDescriptor> descriptors = chars[i].descriptors();
415 for (int j = 0; j < descriptors.count(); j++)
416 QCOMPARE(descriptors[j].isValid(), false);
417 }
418 }
419 }
420
421 qDeleteAll(c: savedReferences);
422 savedReferences.clear();
423}
424
425void tst_QLowEnergyController::tst_concurrentDiscovery()
426{
427#if !defined(Q_OS_MACOS) && !QT_CONFIG(winrt_bt)
428 QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
429 if (localAdapters.isEmpty())
430 QSKIP("No local Bluetooth device found. Skipping test.");
431#endif
432
433 if (!remoteDeviceInfo.isValid())
434 QSKIP("No remote BTLE device found. Skipping test.");
435 QLowEnergyController control(remoteDeviceInfo);
436
437
438 QCOMPARE(control.state(), QLowEnergyController::UnconnectedState);
439 QCOMPARE(control.error(), QLowEnergyController::NoError);
440
441 control.connectToDevice();
442 {
443 QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
444 30000);
445 }
446
447 if (control.state() == QLowEnergyController::ConnectingState
448 || control.error() != QLowEnergyController::NoError) {
449 // default BTLE backend forever hangs in ConnectingState
450 QSKIP("Cannot connect to remote device");
451 }
452
453 QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
454
455 // 2. new controller to same device fails
456 {
457#ifdef Q_OS_DARWIN
458 QLowEnergyController control2(remoteDeviceInfo);
459#else
460 QLowEnergyController control2(remoteDevice);
461#endif
462 control2.connectToDevice();
463 {
464 QTRY_IMPL(control2.state() != QLowEnergyController::ConnectingState,
465 30000);
466 }
467
468#if defined(Q_OS_ANDROID) || defined(Q_OS_DARWIN) || QT_CONFIG(winrt_bt)
469 QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
470 QCOMPARE(control2.state(), QLowEnergyController::ConnectedState);
471 control2.disconnectFromDevice();
472 QTest::qWait(3000);
473 QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
474 QCOMPARE(control2.state(), QLowEnergyController::UnconnectedState);
475#else
476 if (!isBluezDbusLE) {
477 // see QTBUG-42519
478 // Linux non-DBus GATT cannot maintain two controller connections at the same time
479 QCOMPARE(control.state(), QLowEnergyController::UnconnectedState);
480 QCOMPARE(control2.state(), QLowEnergyController::ConnectedState);
481 control2.disconnectFromDevice();
482 QTRY_COMPARE(control2.state(), QLowEnergyController::UnconnectedState);
483 QTRY_COMPARE(control2.error(), QLowEnergyController::NoError);
484
485 // reconnect control
486 control.connectToDevice();
487 {
488 QTRY_VERIFY_WITH_TIMEOUT(control.state() != QLowEnergyController::ConnectingState,
489 30000);
490 }
491 QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
492 } else {
493 QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
494 QCOMPARE(control2.state(), QLowEnergyController::ConnectedState);
495 QTRY_COMPARE(control2.error(), QLowEnergyController::NoError);
496 control2.disconnectFromDevice();
497 QTRY_COMPARE(control2.state(), QLowEnergyController::UnconnectedState);
498 QTRY_COMPARE(control2.error(), QLowEnergyController::NoError);
499 QTRY_COMPARE(control.state(), QLowEnergyController::UnconnectedState);
500 QTRY_COMPARE(control.error(), QLowEnergyController::NoError);
501
502 // reconnect control
503 control.connectToDevice();
504 {
505 QTRY_VERIFY_WITH_TIMEOUT(control.state() != QLowEnergyController::ConnectingState,
506 30000);
507 }
508 QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
509 }
510#endif
511 }
512
513 /* We are testing that we can run service discovery on the same device
514 * for multiple services at the same time.
515 * */
516
517 QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
518 QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
519 control.discoverServices();
520 QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
521 QCOMPARE(stateSpy.count(), 2);
522 QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
523 QLowEnergyController::DiscoveringState);
524 QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
525 QLowEnergyController::DiscoveredState);
526
527 // pick MAX_SERVICES_SAME_TIME_ACCESS services
528 // and discover them at the same time
529#define MAX_SERVICES_SAME_TIME_ACCESS 3
530 QLowEnergyService *services[MAX_SERVICES_SAME_TIME_ACCESS];
531
532 QVERIFY(control.services().count() >= MAX_SERVICES_SAME_TIME_ACCESS);
533
534 QList<QBluetoothUuid> uuids = control.services();
535
536 // initialize services
537 for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) {
538 services[i] = control.createServiceObject(service: uuids.at(i), parent: this);
539 QVERIFY(services[i]);
540 }
541
542 // start complete discovery
543 for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++)
544 services[i]->discoverDetails();
545
546 // wait until discovery done
547 for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) {
548 qWarning() << "Waiting for" << i << services[i]->serviceUuid();
549 QTRY_VERIFY_WITH_TIMEOUT(
550 services[i]->state() == QLowEnergyService::ServiceDiscovered,
551 30000);
552 }
553
554 // verify discovered services
555 for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) {
556 verifyServiceProperties(info: services[i]);
557
558 QVERIFY(!services[i]->contains(QLowEnergyCharacteristic()));
559 QVERIFY(!services[i]->contains(QLowEnergyDescriptor()));
560 }
561
562 control.disconnectFromDevice();
563 QTRY_VERIFY_WITH_TIMEOUT(control.state() == QLowEnergyController::UnconnectedState,
564 30000);
565 discoveryFinishedSpy.clear();
566
567 // redo the discovery with same controller
568 QLowEnergyService *services_second[MAX_SERVICES_SAME_TIME_ACCESS];
569 control.connectToDevice();
570 {
571 QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
572 30000);
573 }
574
575 QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
576 stateSpy.clear();
577 control.discoverServices();
578 QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
579 QCOMPARE(stateSpy.count(), 2);
580 QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
581 QLowEnergyController::DiscoveringState);
582 QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
583 QLowEnergyController::DiscoveredState);
584
585 // get all details
586 for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) {
587 services_second[i] = control.createServiceObject(service: uuids.at(i), parent: this);
588 QVERIFY(services_second[i]->parent() == this);
589 QVERIFY(services[i]);
590 QVERIFY(services_second[i]->state() == QLowEnergyService::DiscoveryRequired);
591 services_second[i]->discoverDetails();
592 }
593
594 // wait until discovery done
595 for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) {
596 qWarning() << "Waiting for" << i << services_second[i]->serviceUuid();
597 QTRY_VERIFY_WITH_TIMEOUT(
598 services_second[i]->state() == QLowEnergyService::ServiceDiscovered,
599 30000);
600 QCOMPARE(services_second[i]->serviceName(), services[i]->serviceName());
601 QCOMPARE(services_second[i]->serviceUuid(), services[i]->serviceUuid());
602 }
603
604 // verify discovered services (1st and 2nd round)
605 for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) {
606 verifyServiceProperties(info: services_second[i]);
607 //after disconnect all related characteristics and descriptors are invalid
608 const QList<QLowEnergyCharacteristic> chars = services[i]->characteristics();
609 for (int j = 0; j < chars.count(); j++) {
610 QCOMPARE(chars.at(j).isValid(), false);
611 QVERIFY(services[i]->contains(chars[j]));
612 QVERIFY(!services_second[i]->contains(chars[j]));
613 const QList<QLowEnergyDescriptor> descriptors = chars[j].descriptors();
614 for (int k = 0; k < descriptors.count(); k++) {
615 QCOMPARE(descriptors[k].isValid(), false);
616 services[i]->contains(descriptor: descriptors[k]);
617 QVERIFY(!services_second[i]->contains(chars[j]));
618 }
619 }
620
621 QCOMPARE(services[i]->serviceUuid(), services_second[i]->serviceUuid());
622 QCOMPARE(services[i]->serviceName(), services_second[i]->serviceName());
623 QCOMPARE(services[i]->type(), services_second[i]->type());
624 QVERIFY(services[i]->state() == QLowEnergyService::InvalidService);
625 QVERIFY(services_second[i]->state() == QLowEnergyService::ServiceDiscovered);
626 }
627
628 // cleanup
629 for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) {
630 delete services[i];
631 delete services_second[i];
632 }
633
634 control.disconnectFromDevice();
635 QTRY_COMPARE(control.state(), QLowEnergyController::UnconnectedState);
636 QCOMPARE(control.error(), QLowEnergyController::NoError);
637}
638
639void tst_QLowEnergyController::verifyServiceProperties(
640 const QLowEnergyService *info)
641{
642 if (info->serviceUuid() ==
643 QBluetoothUuid(QString("00001800-0000-1000-8000-00805f9b34fb"))) {
644 qDebug() << "Verifying GAP Service";
645 QList<QLowEnergyCharacteristic> chars = info->characteristics();
646 QCOMPARE(chars.count(), 5);
647
648 // Device Name
649 QString temp("00002a00-0000-1000-8000-00805f9b34fb");
650 QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
651 HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x3));
652 QCOMPARE(chars[0].properties(), QLowEnergyCharacteristic::Read);
653 QCOMPARE(chars[0].value(), QByteArray::fromHex("544920424c452053656e736f7220546167"));
654 QVERIFY(chars[0].isValid());
655 QCOMPARE(chars[0].descriptors().count(), 0);
656 QVERIFY(info->contains(chars[0]));
657
658 // Appearance
659 temp = QString("00002a01-0000-1000-8000-00805f9b34fb");
660 QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
661 HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x5));
662 QCOMPARE(chars[1].properties(), QLowEnergyCharacteristic::Read);
663 QCOMPARE(chars[1].value(), QByteArray::fromHex("0000"));
664 QVERIFY(chars[1].isValid());
665 QCOMPARE(chars[1].descriptors().count(), 0);
666 QVERIFY(info->contains(chars[1]));
667
668 // Peripheral Privacy Flag
669 temp = QString("00002a02-0000-1000-8000-00805f9b34fb");
670 QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
671 HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x7));
672 QVERIFY(chars[2].properties() & QLowEnergyCharacteristic::Read);
673 QCOMPARE(chars[2].value(), QByteArray::fromHex("00"));
674 QVERIFY(chars[2].isValid());
675 QCOMPARE(chars[2].descriptors().count(), 0);
676 QVERIFY(info->contains(chars[2]));
677
678 // Reconnection Address
679 temp = QString("00002a03-0000-1000-8000-00805f9b34fb");
680 QCOMPARE(chars[3].uuid(), QBluetoothUuid(temp));
681 HANDLE_COMPARE(chars[3].handle(), QLowEnergyHandle(0x9));
682 //Early firmware version had this characteristic as Read|Write and may fail
683 QCOMPARE(chars[3].properties(), QLowEnergyCharacteristic::Write);
684 if (chars[3].properties() & QLowEnergyCharacteristic::Read)
685 QCOMPARE(chars[3].value(), QByteArray::fromHex("000000000000"));
686 else
687 QCOMPARE(chars[3].value(), QByteArray());
688 QVERIFY(chars[3].isValid());
689 QCOMPARE(chars[3].descriptors().count(), 0);
690 QVERIFY(info->contains(chars[3]));
691
692 // Peripheral Preferred Connection Parameters
693 temp = QString("00002a04-0000-1000-8000-00805f9b34fb");
694 QCOMPARE(chars[4].uuid(), QBluetoothUuid(temp));
695 HANDLE_COMPARE(chars[4].handle(), QLowEnergyHandle(0xb));
696 QCOMPARE(chars[4].properties(), QLowEnergyCharacteristic::Read);
697 QCOMPARE(chars[4].value(), QByteArray::fromHex("5000a0000000e803"));
698 QVERIFY(chars[4].isValid());
699 QCOMPARE(chars[4].descriptors().count(), 0);
700 QVERIFY(info->contains(chars[4]));
701 } else if (info->serviceUuid() ==
702 QBluetoothUuid(QString("00001801-0000-1000-8000-00805f9b34fb"))) {
703 qDebug() << "Verifying GATT Service";
704 QList<QLowEnergyCharacteristic> chars = info->characteristics();
705 QCOMPARE(chars.count(), 1);
706
707 // Service Changed
708 QString temp("00002a05-0000-1000-8000-00805f9b34fb");
709 //this should really be readable according to GATT Service spec
710 QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
711 HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0xe));
712 QCOMPARE(chars[0].properties(), QLowEnergyCharacteristic::Indicate);
713 QCOMPARE(chars[0].value(), QByteArray());
714 QVERIFY(chars[0].isValid());
715 QVERIFY(info->contains(chars[0]));
716
717 QCOMPARE(chars[0].descriptors().count(), 1);
718 QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
719 HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0xf));
720 QCOMPARE(chars[0].descriptors().at(0).uuid(),
721 QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
722 QCOMPARE(chars[0].descriptors().at(0).type(),
723 QBluetoothUuid::ClientCharacteristicConfiguration);
724 QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
725 QVERIFY(info->contains(chars[0].descriptors().at(0)));
726 } else if (info->serviceUuid() ==
727 QBluetoothUuid(QString("0000180a-0000-1000-8000-00805f9b34fb"))) {
728 qDebug() << "Verifying Device Information";
729 QList<QLowEnergyCharacteristic> chars = info->characteristics();
730 QCOMPARE(chars.count(), 9);
731
732 // System ID
733 QString temp("00002a23-0000-1000-8000-00805f9b34fb");
734 //this should really be readable according to GATT Service spec
735 QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
736 HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x12));
737 QCOMPARE(chars[0].properties(), QLowEnergyCharacteristic::Read);
738// Do not read the System ID as it is different for every device
739// QEXPECT_FAIL("", "The value is different on different devices", Continue);
740// QCOMPARE(chars[0].value(), QByteArray::fromHex("6e41ab0000296abc"));
741 QVERIFY(chars[0].isValid());
742 QVERIFY(info->contains(chars[0]));
743 QCOMPARE(chars[0].descriptors().count(), 0);
744
745 // Model Number
746 temp = QString("00002a24-0000-1000-8000-00805f9b34fb");
747 QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
748 HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x14));
749 QCOMPARE(chars[1].properties(), QLowEnergyCharacteristic::Read);
750 QCOMPARE(chars[1].value(), QByteArray::fromHex("4e2e412e00"));
751 QVERIFY(chars[1].isValid());
752 QVERIFY(info->contains(chars[1]));
753 QCOMPARE(chars[1].descriptors().count(), 0);
754
755 // Serial Number
756 temp = QString("00002a25-0000-1000-8000-00805f9b34fb");
757 QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
758 HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x16));
759 QCOMPARE(chars[2].properties(),
760 (QLowEnergyCharacteristic::Read));
761 QCOMPARE(chars[2].value(), QByteArray::fromHex("4e2e412e00"));
762 QVERIFY(chars[2].isValid());
763 QVERIFY(info->contains(chars[2]));
764 QCOMPARE(chars[2].descriptors().count(), 0);
765
766 // Firmware Revision
767 temp = QString("00002a26-0000-1000-8000-00805f9b34fb");
768 QCOMPARE(chars[3].uuid(), QBluetoothUuid(temp));
769 HANDLE_COMPARE(chars[3].handle(), QLowEnergyHandle(0x18));
770 QCOMPARE(chars[3].properties(),
771 (QLowEnergyCharacteristic::Read));
772 //FW rev. : 1.5 (Oct 23 2013)
773 // Other revisions will fail here
774 QCOMPARE(chars[3].value(), QByteArray::fromHex("312e3520284f637420323320323031332900"));
775 QVERIFY(chars[3].isValid());
776 QVERIFY(info->contains(chars[3]));
777 QCOMPARE(chars[3].descriptors().count(), 0);
778
779 // Hardware Revision
780 temp = QString("00002a27-0000-1000-8000-00805f9b34fb");
781 QCOMPARE(chars[4].uuid(), QBluetoothUuid(temp));
782 HANDLE_COMPARE(chars[4].handle(), QLowEnergyHandle(0x1a));
783 QCOMPARE(chars[4].properties(),
784 (QLowEnergyCharacteristic::Read));
785 QCOMPARE(chars[4].value(), QByteArray::fromHex("4e2e412e00"));
786 QVERIFY(chars[4].isValid());
787 QVERIFY(info->contains(chars[4]));
788 QCOMPARE(chars[4].descriptors().count(), 0);
789
790 // Software Revision
791 temp = QString("00002a28-0000-1000-8000-00805f9b34fb");
792 QCOMPARE(chars[5].uuid(), QBluetoothUuid(temp));
793 HANDLE_COMPARE(chars[5].handle(), QLowEnergyHandle(0x1c));
794 QCOMPARE(chars[5].properties(),
795 (QLowEnergyCharacteristic::Read));
796 QCOMPARE(chars[5].value(), QByteArray::fromHex("4e2e412e00"));
797 QVERIFY(chars[5].isValid());
798 QVERIFY(info->contains(chars[5]));
799 QCOMPARE(chars[5].descriptors().count(), 0);
800
801 // Manufacturer Name
802 temp = QString("00002a29-0000-1000-8000-00805f9b34fb");
803 QCOMPARE(chars[6].uuid(), QBluetoothUuid(temp));
804 HANDLE_COMPARE(chars[6].handle(), QLowEnergyHandle(0x1e));
805 QCOMPARE(chars[6].properties(),
806 (QLowEnergyCharacteristic::Read));
807 QCOMPARE(chars[6].value(), QByteArray::fromHex("546578617320496e737472756d656e747300"));
808 QVERIFY(chars[6].isValid());
809 QVERIFY(info->contains(chars[6]));
810 QCOMPARE(chars[6].descriptors().count(), 0);
811
812 // IEEE
813 temp = QString("00002a2a-0000-1000-8000-00805f9b34fb");
814 QCOMPARE(chars[7].uuid(), QBluetoothUuid(temp));
815 HANDLE_COMPARE(chars[7].handle(), QLowEnergyHandle(0x20));
816 QCOMPARE(chars[7].properties(),
817 (QLowEnergyCharacteristic::Read));
818 QCOMPARE(chars[7].value(), QByteArray::fromHex("fe006578706572696d656e74616c"));
819 QVERIFY(chars[7].isValid());
820 QVERIFY(info->contains(chars[7]));
821 QCOMPARE(chars[7].descriptors().count(), 0);
822
823 // PnP ID
824 temp = QString("00002a50-0000-1000-8000-00805f9b34fb");
825 QCOMPARE(chars[8].uuid(), QBluetoothUuid(temp));
826 HANDLE_COMPARE(chars[8].handle(), QLowEnergyHandle(0x22));
827 QCOMPARE(chars[8].properties(),
828 (QLowEnergyCharacteristic::Read));
829 QCOMPARE(chars[8].value(), QByteArray::fromHex("010d0000001001"));
830 QVERIFY(chars[8].isValid());
831 QVERIFY(info->contains(chars[8]));
832 QCOMPARE(chars[8].descriptors().count(), 0);
833 } else if (info->serviceUuid() ==
834 QBluetoothUuid(QString("f000aa00-0451-4000-b000-000000000000"))) {
835 qDebug() << "Verifying Temperature";
836 QList<QLowEnergyCharacteristic> chars = info->characteristics();
837 QVERIFY(chars.count() >= 2);
838
839 // Temp Data
840 QString temp("f000aa01-0451-4000-b000-000000000000");
841 QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
842 HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x25));
843 QCOMPARE(chars[0].properties(),
844 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify));
845 QCOMPARE(chars[0].value(), QByteArray::fromHex("00000000"));
846 QVERIFY(chars[0].isValid());
847 QVERIFY(info->contains(chars[0]));
848
849 QCOMPARE(chars[0].descriptors().count(), 2);
850 //descriptor checks
851 QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
852 HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x26));
853 QCOMPARE(chars[0].descriptors().at(0).uuid(),
854 QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
855 QCOMPARE(chars[0].descriptors().at(0).type(),
856 QBluetoothUuid::ClientCharacteristicConfiguration);
857 QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
858 QVERIFY(info->contains(chars[0].descriptors().at(0)));
859
860 QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
861 HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x27));
862 QCOMPARE(chars[0].descriptors().at(1).uuid(),
863 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
864 QCOMPARE(chars[0].descriptors().at(1).type(),
865 QBluetoothUuid::CharacteristicUserDescription);
866 // value different in other revisions and test may fail
867 QCOMPARE(chars[0].descriptors().at(1).value(),
868 QByteArray::fromHex("54656d702e2044617461"));
869 QVERIFY(info->contains(chars[0].descriptors().at(1)));
870
871 // Temp Config
872 temp = QString("f000aa02-0451-4000-b000-000000000000");
873 QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
874 HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x29));
875 QCOMPARE(chars[1].properties(),
876 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
877 QCOMPARE(chars[1].value(), QByteArray::fromHex("00"));
878 QVERIFY(chars[1].isValid());
879 QVERIFY(info->contains(chars[1]));
880
881 QCOMPARE(chars[1].descriptors().count(), 1);
882 //descriptor checks
883 QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
884 HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x2a));
885 QCOMPARE(chars[1].descriptors().at(0).uuid(),
886 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
887 QCOMPARE(chars[1].descriptors().at(0).type(),
888 QBluetoothUuid::CharacteristicUserDescription);
889 // value different in other revisions and test may fail
890 QCOMPARE(chars[1].descriptors().at(0).value(),
891 QByteArray::fromHex("54656d702e20436f6e662e"));
892 QVERIFY(info->contains(chars[1].descriptors().at(0)));
893
894
895 //Temp Period (introduced by later firmware versions)
896 if (chars.count() > 2) {
897 temp = QString("f000aa03-0451-4000-b000-000000000000");
898 QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
899 HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x2c));
900 QCOMPARE(chars[2].properties(),
901 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
902 QCOMPARE(chars[2].value(), QByteArray::fromHex("64"));
903 QVERIFY(chars[2].isValid());
904 QVERIFY(info->contains(chars[2]));
905
906 QCOMPARE(chars[2].descriptors().count(), 1);
907 //descriptor checks
908 QCOMPARE(chars[2].descriptors().at(0).isValid(), true);
909 HANDLE_COMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x2d));
910 QCOMPARE(chars[2].descriptors().at(0).uuid(),
911 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
912 QCOMPARE(chars[2].descriptors().at(0).type(),
913 QBluetoothUuid::CharacteristicUserDescription);
914 QCOMPARE(chars[2].descriptors().at(0).value(),
915 QByteArray::fromHex("54656d702e20506572696f64"));
916 QVERIFY(info->contains(chars[2].descriptors().at(0)));
917 }
918 } else if (info->serviceUuid() ==
919 QBluetoothUuid(QString("0000ffe0-0000-1000-8000-00805f9b34fb"))) {
920 qDebug() << "Verifying Simple Keys";
921 QList<QLowEnergyCharacteristic> chars = info->characteristics();
922 QCOMPARE(chars.count(), 1);
923
924 // Temp Data
925 QString temp("0000ffe1-0000-1000-8000-00805f9b34fb");
926 QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
927 // value different in other revisions and test may fail
928 HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x6b));
929 QCOMPARE(chars[0].properties(),
930 (QLowEnergyCharacteristic::Notify));
931 QCOMPARE(chars[0].value(), QByteArray());
932 QVERIFY(chars[0].isValid());
933 QVERIFY(info->contains(chars[0]));
934
935 QCOMPARE(chars[0].descriptors().count(), 2);
936 //descriptor checks
937 QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
938 // value different in other revisions and test may fail
939 HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x6c));
940 QCOMPARE(chars[0].descriptors().at(0).uuid(),
941 QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
942 QCOMPARE(chars[0].descriptors().at(0).type(),
943 QBluetoothUuid::ClientCharacteristicConfiguration);
944 QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
945 QVERIFY(info->contains(chars[0].descriptors().at(0)));
946
947 QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
948 // value different in other revisions and test may fail
949 HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x6d));
950 QCOMPARE(chars[0].descriptors().at(1).uuid(),
951 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
952 QCOMPARE(chars[0].descriptors().at(1).type(),
953 QBluetoothUuid::CharacteristicUserDescription);
954 QCOMPARE(chars[0].descriptors().at(1).value(),
955 QByteArray::fromHex("4b6579205072657373205374617465"));
956 QVERIFY(info->contains(chars[0].descriptors().at(1)));
957
958 } else if (info->serviceUuid() ==
959 QBluetoothUuid(QString("f000aa10-0451-4000-b000-000000000000"))) {
960 qDebug() << "Verifying Accelerometer";
961 QList<QLowEnergyCharacteristic> chars = info->characteristics();
962 QCOMPARE(chars.count(), 3);
963
964 // Accel Data
965 QString temp("f000aa11-0451-4000-b000-000000000000");
966 QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
967 // value different in other revisions and test may fail
968 HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x30));
969 QCOMPARE(chars[0].properties(),
970 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify));
971 QCOMPARE(chars[0].value(), QByteArray::fromHex("000000"));
972 QVERIFY(chars[0].isValid());
973 QVERIFY(info->contains(chars[0]));
974
975 QCOMPARE(chars[0].descriptors().count(), 2);
976
977 QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
978 // value different in other revisions and test may fail
979 HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x31));
980 QCOMPARE(chars[0].descriptors().at(0).uuid(),
981 QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
982 QCOMPARE(chars[0].descriptors().at(0).type(),
983 QBluetoothUuid::ClientCharacteristicConfiguration);
984 QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
985 QVERIFY(info->contains(chars[0].descriptors().at(0)));
986
987 QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
988 // value different in other revisions and test may fail
989 HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x32));
990 QCOMPARE(chars[0].descriptors().at(1).uuid(),
991 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
992 QCOMPARE(chars[0].descriptors().at(1).type(),
993 QBluetoothUuid::CharacteristicUserDescription);
994 QCOMPARE(chars[0].descriptors().at(1).value(),
995 QByteArray::fromHex("416363656c2e2044617461"));
996 QVERIFY(info->contains(chars[0].descriptors().at(1)));
997
998 // Accel Config
999 temp = QString("f000aa12-0451-4000-b000-000000000000");
1000 QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
1001 // value different in other revisions and test may fail
1002 HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x34));
1003 QCOMPARE(chars[1].properties(),
1004 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
1005 QCOMPARE(chars[1].value(), QByteArray::fromHex("00"));
1006 QVERIFY(chars[1].isValid());
1007 QVERIFY(info->contains(chars[1]));
1008 QCOMPARE(chars[1].descriptors().count(), 1);
1009
1010 QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
1011 // value different in other revisions and test may fail
1012 HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x35));
1013 QCOMPARE(chars[1].descriptors().at(0).uuid(),
1014 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1015 QCOMPARE(chars[1].descriptors().at(0).type(),
1016 QBluetoothUuid::CharacteristicUserDescription);
1017 QCOMPARE(chars[1].descriptors().at(0).value(),
1018 QByteArray::fromHex("416363656c2e20436f6e662e"));
1019 QVERIFY(info->contains(chars[1].descriptors().at(0)));
1020
1021 // Accel Period
1022 temp = QString("f000aa13-0451-4000-b000-000000000000");
1023 QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
1024 // value different in other revisions and test may fail
1025 HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x37));
1026 QCOMPARE(chars[2].properties(),
1027 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
1028 QCOMPARE(chars[2].value(), QByteArray::fromHex("64")); // don't change it or set it to 0x64
1029 QVERIFY(chars[2].isValid());
1030 QVERIFY(info->contains(chars[2]));
1031
1032 QCOMPARE(chars[2].descriptors().count(), 1);
1033 //descriptor checks
1034 QCOMPARE(chars[2].descriptors().at(0).isValid(), true);
1035 // value different in other revisions and test may fail
1036 HANDLE_COMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x38));
1037 QCOMPARE(chars[2].descriptors().at(0).uuid(),
1038 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1039 QCOMPARE(chars[2].descriptors().at(0).type(),
1040 QBluetoothUuid::CharacteristicUserDescription);
1041 // value different in other revisions and test may fail
1042 QCOMPARE(chars[2].descriptors().at(0).value(),
1043 QByteArray::fromHex("416363656c2e20506572696f64"));
1044 QVERIFY(info->contains(chars[2].descriptors().at(0)));
1045 } else if (info->serviceUuid() ==
1046 QBluetoothUuid(QString("f000aa20-0451-4000-b000-000000000000"))) {
1047 qDebug() << "Verifying Humidity";
1048 QList<QLowEnergyCharacteristic> chars = info->characteristics();
1049 QVERIFY(chars.count() >= 2); //new firmware has more chars
1050
1051 // Humidity Data
1052 QString temp("f000aa21-0451-4000-b000-000000000000");
1053 QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
1054 // value different in other revisions and test may fail
1055 HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x3b));
1056 QCOMPARE(chars[0].properties(),
1057 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify));
1058 QCOMPARE(chars[0].value(), QByteArray::fromHex("00000000"));
1059 QVERIFY(chars[0].isValid());
1060 QVERIFY(info->contains(chars[0]));
1061
1062 QCOMPARE(chars[0].descriptors().count(), 2);
1063 //descriptor checks
1064 QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
1065 // value different in other revisions and test may fail
1066 HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x3c));
1067 QCOMPARE(chars[0].descriptors().at(0).uuid(),
1068 QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
1069 QCOMPARE(chars[0].descriptors().at(0).type(),
1070 QBluetoothUuid::ClientCharacteristicConfiguration);
1071 QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
1072 QVERIFY(info->contains(chars[0].descriptors().at(0)));
1073
1074 QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
1075 // value different in other revisions and test may fail
1076 HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x3d));
1077 QCOMPARE(chars[0].descriptors().at(1).uuid(),
1078 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1079 QCOMPARE(chars[0].descriptors().at(1).type(),
1080 QBluetoothUuid::CharacteristicUserDescription);
1081 QCOMPARE(chars[0].descriptors().at(1).value(),
1082 QByteArray::fromHex("48756d69642e2044617461"));
1083 QVERIFY(info->contains(chars[0].descriptors().at(1)));
1084
1085 // Humidity Config
1086 temp = QString("f000aa22-0451-4000-b000-000000000000");
1087 QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
1088 // value different in other revisions and test may fail
1089 HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x3f));
1090 QCOMPARE(chars[1].properties(),
1091 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
1092 QCOMPARE(chars[1].value(), QByteArray::fromHex("00"));
1093 QVERIFY(chars[1].isValid());
1094 QVERIFY(info->contains(chars[1]));
1095
1096 QCOMPARE(chars[1].descriptors().count(), 1);
1097 //descriptor checks
1098 QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
1099 // value different in other revisions and test may fail
1100 HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x40));
1101 QCOMPARE(chars[1].descriptors().at(0).uuid(),
1102 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1103 QCOMPARE(chars[1].descriptors().at(0).type(),
1104 QBluetoothUuid::CharacteristicUserDescription);
1105 QCOMPARE(chars[1].descriptors().at(0).value(),
1106 QByteArray::fromHex("48756d69642e20436f6e662e"));
1107 QVERIFY(info->contains(chars[1].descriptors().at(0)));
1108
1109 if (chars.count() >= 3) {
1110 // New firmware new characteristic
1111 // Humidity Period
1112 temp = QString("f000aa23-0451-4000-b000-000000000000");
1113 QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
1114 HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x42));
1115 QCOMPARE(chars[2].properties(),
1116 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
1117 QCOMPARE(chars[2].value(), QByteArray::fromHex("64"));
1118 QVERIFY(chars[2].isValid());
1119 QVERIFY(info->contains(chars[2]));
1120
1121 QCOMPARE(chars[2].descriptors().count(), 1);
1122 //descriptor checks
1123 QCOMPARE(chars[2].descriptors().at(0).isValid(), true);
1124 HANDLE_COMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x43));
1125 QCOMPARE(chars[2].descriptors().at(0).uuid(),
1126 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1127 QCOMPARE(chars[2].descriptors().at(0).type(),
1128 QBluetoothUuid::CharacteristicUserDescription);
1129 QCOMPARE(chars[2].descriptors().at(0).value(),
1130 QByteArray::fromHex("48756d69642e20506572696f64"));
1131 QVERIFY(info->contains(chars[2].descriptors().at(0)));
1132 }
1133 } else if (info->serviceUuid() ==
1134 QBluetoothUuid(QString("f000aa30-0451-4000-b000-000000000000"))) {
1135 qDebug() << "Verifying Magnetometer";
1136 QList<QLowEnergyCharacteristic> chars = info->characteristics();
1137 QCOMPARE(chars.count(), 3);
1138
1139 // Magnetometer Data
1140 QString temp("f000aa31-0451-4000-b000-000000000000");
1141 QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
1142 // value different in other revisions and test may fail
1143 HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x46));
1144 QCOMPARE(chars[0].properties(),
1145 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify));
1146 QCOMPARE(chars[0].value(), QByteArray::fromHex("000000000000"));
1147 QVERIFY(chars[0].isValid());
1148 QVERIFY(info->contains(chars[0]));
1149
1150 QCOMPARE(chars[0].descriptors().count(), 2);
1151
1152 QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
1153 // value different in other revisions and test may fail
1154 HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x47));
1155 QCOMPARE(chars[0].descriptors().at(0).uuid(),
1156 QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
1157 QCOMPARE(chars[0].descriptors().at(0).type(),
1158 QBluetoothUuid::ClientCharacteristicConfiguration);
1159 QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
1160 QVERIFY(info->contains(chars[0].descriptors().at(0)));
1161
1162 QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
1163 // value different in other revisions and test may fail
1164 HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x48));
1165 QCOMPARE(chars[0].descriptors().at(1).uuid(),
1166 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1167 QCOMPARE(chars[0].descriptors().at(1).type(),
1168 QBluetoothUuid::CharacteristicUserDescription);
1169 QCOMPARE(chars[0].descriptors().at(1).value(),
1170 QByteArray::fromHex("4d61676e2e2044617461"));
1171 QVERIFY(info->contains(chars[0].descriptors().at(1)));
1172
1173 // Magnetometer Config
1174 temp = QString("f000aa32-0451-4000-b000-000000000000");
1175 QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
1176 // value different in other revisions and test may fail
1177 HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x4a));
1178 QCOMPARE(chars[1].properties(),
1179 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
1180 QCOMPARE(chars[1].value(), QByteArray::fromHex("00"));
1181 QVERIFY(chars[1].isValid());
1182 QVERIFY(info->contains(chars[1]));
1183
1184 QCOMPARE(chars[1].descriptors().count(), 1);
1185 QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
1186 // value different in other revisions and test may fail
1187 HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x4b));
1188 QCOMPARE(chars[1].descriptors().at(0).uuid(),
1189 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1190 QCOMPARE(chars[1].descriptors().at(0).type(),
1191 QBluetoothUuid::CharacteristicUserDescription);
1192 // value different in other revisions and test may fail
1193 QCOMPARE(chars[1].descriptors().at(0).value(),
1194 QByteArray::fromHex("4d61676e2e20436f6e662e"));
1195 QVERIFY(info->contains(chars[1].descriptors().at(0)));
1196
1197 // Magnetometer Period
1198 temp = QString("f000aa33-0451-4000-b000-000000000000");
1199 QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
1200 // value different in other revisions and test may fail
1201 HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x4d));
1202 QCOMPARE(chars[2].properties(),
1203 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
1204 QCOMPARE(chars[2].value(), QByteArray::fromHex("c8")); // don't change it or set it to 0xc8
1205 QVERIFY(chars[2].isValid());
1206 QVERIFY(info->contains(chars[2]));
1207
1208 QCOMPARE(chars[2].descriptors().count(), 1);
1209 QCOMPARE(chars[2].descriptors().at(0).isValid(), true);
1210 // value different in other revisions and test may fail
1211 HANDLE_COMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x4e));
1212 QCOMPARE(chars[2].descriptors().at(0).uuid(),
1213 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1214 QCOMPARE(chars[2].descriptors().at(0).type(),
1215 QBluetoothUuid::CharacteristicUserDescription);
1216 // value different in other revisions and test may fail
1217 QCOMPARE(chars[2].descriptors().at(0).value(),
1218 QByteArray::fromHex("4d61676e2e20506572696f64"));
1219 QVERIFY(info->contains(chars[2].descriptors().at(0)));
1220 } else if (info->serviceUuid() ==
1221 QBluetoothUuid(QString("f000aa40-0451-4000-b000-000000000000"))) {
1222 qDebug() << "Verifying Pressure";
1223 const QList<QLowEnergyCharacteristic> chars = info->characteristics();
1224 QVERIFY(chars.count() >= 3);
1225
1226 // Pressure Data
1227 QString temp("f000aa41-0451-4000-b000-000000000000");
1228 QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
1229 // value different in other revisions and test may fail
1230 HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x51));
1231 QCOMPARE(chars[0].properties(),
1232 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify));
1233 QCOMPARE(chars[0].value(), QByteArray::fromHex("00000000"));
1234 QVERIFY(chars[0].isValid());
1235 QVERIFY(info->contains(chars[0]));
1236
1237 QCOMPARE(chars[0].descriptors().count(), 2);
1238 //descriptor checks
1239 QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
1240 // value different in other revisions and test may fail
1241 HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x52));
1242 QCOMPARE(chars[0].descriptors().at(0).uuid(),
1243 QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
1244 QCOMPARE(chars[0].descriptors().at(0).type(),
1245 QBluetoothUuid::ClientCharacteristicConfiguration);
1246 QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
1247 QVERIFY(info->contains(chars[0].descriptors().at(0)));
1248
1249 QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
1250 // value different in other revisions and test may fail
1251 HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x53));
1252 QCOMPARE(chars[0].descriptors().at(1).uuid(),
1253 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1254 QCOMPARE(chars[0].descriptors().at(1).type(),
1255 QBluetoothUuid::CharacteristicUserDescription);
1256 // value different in other revisions and test may fail
1257 QCOMPARE(chars[0].descriptors().at(1).value(),
1258 QByteArray::fromHex("4261726f6d2e2044617461"));
1259 QVERIFY(info->contains(chars[0].descriptors().at(1)));
1260
1261 // Pressure Config
1262 temp = QString("f000aa42-0451-4000-b000-000000000000");
1263 QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
1264 // value different in other revisions and test may fail
1265 HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x55));
1266 QCOMPARE(chars[1].properties(),
1267 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
1268 QCOMPARE(chars[1].value(), QByteArray::fromHex("00"));
1269 QVERIFY(chars[1].isValid());
1270 QVERIFY(info->contains(chars[1]));
1271
1272 QCOMPARE(chars[1].descriptors().count(), 1);
1273 QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
1274 // value different in other revisions and test may fail
1275 HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x56));
1276 QCOMPARE(chars[1].descriptors().at(0).uuid(),
1277 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1278 QCOMPARE(chars[1].descriptors().at(0).type(),
1279 QBluetoothUuid::CharacteristicUserDescription);
1280 QCOMPARE(chars[1].descriptors().at(0).value(),
1281 QByteArray::fromHex("4261726f6d2e20436f6e662e"));
1282 QVERIFY(info->contains(chars[1].descriptors().at(0)));
1283
1284 //calibration and period characteristic are swapped, ensure we don't depend on their order
1285 QLowEnergyCharacteristic calibration, period;
1286 for (const QLowEnergyCharacteristic &ch : chars) {
1287 //find calibration characteristic
1288 if (ch.uuid() == QBluetoothUuid(QString("f000aa43-0451-4000-b000-000000000000")))
1289 calibration = ch;
1290 else if (ch.uuid() == QBluetoothUuid(QString("f000aa44-0451-4000-b000-000000000000")))
1291 period = ch;
1292 }
1293
1294 if (calibration.isValid()) {
1295 // Pressure Calibration
1296 temp = QString("f000aa43-0451-4000-b000-000000000000");
1297 QCOMPARE(calibration.uuid(), QBluetoothUuid(temp));
1298 // value different in other revisions and test may fail
1299 HANDLE_COMPARE(calibration.handle(), QLowEnergyHandle(0x5b));
1300 QCOMPARE(calibration.properties(),
1301 (QLowEnergyCharacteristic::Read));
1302 QCOMPARE(calibration.value(), QByteArray::fromHex("00000000000000000000000000000000")); // don't change it
1303 QVERIFY(calibration.isValid());
1304 QVERIFY(info->contains(calibration));
1305
1306 QCOMPARE(calibration.descriptors().count(), 2);
1307 //descriptor checks
1308 QCOMPARE(calibration.descriptors().at(0).isValid(), true);
1309 // value different in other revisions and test may fail
1310 HANDLE_COMPARE(calibration.descriptors().at(0).handle(), QLowEnergyHandle(0x5c));
1311 QCOMPARE(calibration.descriptors().at(0).uuid(),
1312 QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
1313 QCOMPARE(calibration.descriptors().at(0).type(),
1314 QBluetoothUuid::ClientCharacteristicConfiguration);
1315 QVERIFY(verifyClientCharacteristicValue(calibration.descriptors().at(0).value()));
1316 QVERIFY(info->contains(calibration.descriptors().at(0)));
1317
1318 QCOMPARE(calibration.descriptors().at(1).isValid(), true);
1319 // value different in other revisions and test may fail
1320 HANDLE_COMPARE(calibration.descriptors().at(1).handle(), QLowEnergyHandle(0x5d));
1321 QCOMPARE(calibration.descriptors().at(1).uuid(),
1322 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1323 QCOMPARE(calibration.descriptors().at(1).type(),
1324 QBluetoothUuid::CharacteristicUserDescription);
1325 QCOMPARE(calibration.descriptors().at(1).value(),
1326 QByteArray::fromHex("4261726f6d2e2043616c6962722e"));
1327 QVERIFY(info->contains(calibration.descriptors().at(1)));
1328 }
1329
1330 if (period.isValid()) {
1331 // Period Calibration
1332 temp = QString("f000aa44-0451-4000-b000-000000000000");
1333 QCOMPARE(period.uuid(), QBluetoothUuid(temp));
1334 // value different in other revisions and test may fail
1335 HANDLE_COMPARE(period.handle(), QLowEnergyHandle(0x58));
1336 QCOMPARE(period.properties(),
1337 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
1338 QCOMPARE(period.value(), QByteArray::fromHex("64"));
1339 QVERIFY(period.isValid());
1340 QVERIFY(info->contains(period));
1341
1342 QCOMPARE(period.descriptors().count(), 1);
1343 //descriptor checks
1344 QCOMPARE(period.descriptors().at(0).isValid(), true);
1345 // value different in other revisions and test may fail
1346 HANDLE_COMPARE(period.descriptors().at(0).handle(), QLowEnergyHandle(0x59));
1347 QCOMPARE(period.descriptors().at(0).uuid(),
1348 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1349 QCOMPARE(period.descriptors().at(0).type(),
1350 QBluetoothUuid::CharacteristicUserDescription);
1351 QCOMPARE(period.descriptors().at(0).value(),
1352 QByteArray::fromHex("4261726f6d2e20506572696f64"));
1353 QVERIFY(info->contains(period.descriptors().at(0)));
1354 }
1355 } else if (info->serviceUuid() ==
1356 QBluetoothUuid(QString("f000aa50-0451-4000-b000-000000000000"))) {
1357 qDebug() << "Verifying Gyroscope";
1358 QList<QLowEnergyCharacteristic> chars = info->characteristics();
1359 QVERIFY(chars.count() >= 2);
1360
1361 // Gyroscope Data
1362 QString temp("f000aa51-0451-4000-b000-000000000000");
1363 QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
1364 // value different in other revisions and test may fail
1365 HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x60));
1366 QCOMPARE(chars[0].properties(),
1367 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify));
1368 QCOMPARE(chars[0].value(), QByteArray::fromHex("000000000000"));
1369 QVERIFY(chars[0].isValid());
1370 QVERIFY(info->contains(chars[0]));
1371
1372 QCOMPARE(chars[0].descriptors().count(), 2);
1373 //descriptor checks
1374 QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
1375 // value different in other revisions and test may fail
1376 HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x61));
1377 QCOMPARE(chars[0].descriptors().at(0).uuid(),
1378 QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
1379 QCOMPARE(chars[0].descriptors().at(0).type(),
1380 QBluetoothUuid::ClientCharacteristicConfiguration);
1381 QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
1382 QVERIFY(info->contains(chars[0].descriptors().at(0)));
1383
1384 QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
1385 // value different in other revisions and test may fail
1386 HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x62));
1387 QCOMPARE(chars[0].descriptors().at(1).uuid(),
1388 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1389 QCOMPARE(chars[0].descriptors().at(1).type(),
1390 QBluetoothUuid::CharacteristicUserDescription);
1391 // value different in other revisions and test may fail
1392 QCOMPARE(chars[0].descriptors().at(1).value(),
1393 QByteArray::fromHex("4779726f2044617461"));
1394 QVERIFY(info->contains(chars[0].descriptors().at(1)));
1395
1396 // Gyroscope Config
1397 temp = QString("f000aa52-0451-4000-b000-000000000000");
1398 QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
1399 // value different in other revisions and test may fail
1400 HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x64));
1401 QCOMPARE(chars[1].properties(),
1402 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
1403 QCOMPARE(chars[1].value(), QByteArray::fromHex("00"));
1404 QVERIFY(chars[1].isValid());
1405 QVERIFY(info->contains(chars[1]));
1406
1407 QCOMPARE(chars[1].descriptors().count(), 1);
1408 //descriptor checks
1409 QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
1410 // value different in other revisions and test may fail
1411 HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x65));
1412 QCOMPARE(chars[1].descriptors().at(0).uuid(),
1413 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1414 QCOMPARE(chars[1].descriptors().at(0).type(),
1415 QBluetoothUuid::CharacteristicUserDescription);
1416 QCOMPARE(chars[1].descriptors().at(0).value(),
1417 QByteArray::fromHex("4779726f20436f6e662e"));
1418 QVERIFY(info->contains(chars[1].descriptors().at(0)));
1419
1420 // Gyroscope Period
1421 temp = QString("f000aa53-0451-4000-b000-000000000000");
1422 QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
1423 // value different in other revisions and test may fail
1424 HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x67));
1425 QCOMPARE(chars[2].properties(),
1426 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
1427 QCOMPARE(chars[2].value(), QByteArray::fromHex("64"));
1428 QVERIFY(chars[2].isValid());
1429 QVERIFY(info->contains(chars[2]));
1430
1431 QCOMPARE(chars[2].descriptors().count(), 1);
1432 //descriptor checks
1433 QCOMPARE(chars[2].descriptors().at(0).isValid(), true);
1434 HANDLE_COMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x68));
1435 QCOMPARE(chars[2].descriptors().at(0).uuid(),
1436 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1437 QCOMPARE(chars[2].descriptors().at(0).type(),
1438 QBluetoothUuid::CharacteristicUserDescription);
1439 QCOMPARE(chars[2].descriptors().at(0).value(),
1440 QByteArray::fromHex("4779726f20506572696f64"));
1441 QVERIFY(info->contains(chars[2].descriptors().at(0)));
1442 } else if (info->serviceUuid() ==
1443 QBluetoothUuid(QString("f000aa60-0451-4000-b000-000000000000"))) {
1444 qDebug() << "Verifying Test Service";
1445 QList<QLowEnergyCharacteristic> chars = info->characteristics();
1446 QCOMPARE(chars.count(), 2);
1447
1448 // Test Data
1449 QString temp("f000aa61-0451-4000-b000-000000000000");
1450 QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
1451 // value different in other revisions and test may fail
1452 HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x70));
1453 QCOMPARE(chars[0].properties(),
1454 (QLowEnergyCharacteristic::Read));
1455 QCOMPARE(chars[0].value(), QByteArray::fromHex("3f00"));
1456 QVERIFY(chars[0].isValid());
1457 QVERIFY(info->contains(chars[0]));
1458
1459 QCOMPARE(chars[0].descriptors().count(), 1);
1460 QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
1461 // value different in other revisions and test may fail
1462 HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x71));
1463 QCOMPARE(chars[0].descriptors().at(0).uuid(),
1464 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1465 QCOMPARE(chars[0].descriptors().at(0).type(),
1466 QBluetoothUuid::CharacteristicUserDescription);
1467 QCOMPARE(chars[0].descriptors().at(0).value(),
1468 QByteArray::fromHex("546573742044617461"));
1469 QVERIFY(info->contains(chars[0].descriptors().at(0)));
1470
1471 // Test Config
1472 temp = QString("f000aa62-0451-4000-b000-000000000000");
1473 QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
1474 HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x73));
1475 QCOMPARE(chars[1].properties(),
1476 (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write));
1477 QCOMPARE(chars[1].value(), QByteArray::fromHex("00"));
1478 QVERIFY(chars[1].isValid());
1479 QVERIFY(info->contains(chars[1]));
1480
1481 QCOMPARE(chars[1].descriptors().count(), 1);
1482 //descriptor checks
1483 QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
1484 // value different in other revisions and test may fail
1485 HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x74));
1486 QCOMPARE(chars[1].descriptors().at(0).uuid(),
1487 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1488 QCOMPARE(chars[1].descriptors().at(0).type(),
1489 QBluetoothUuid::CharacteristicUserDescription);
1490 QCOMPARE(chars[1].descriptors().at(0).value(),
1491 QByteArray::fromHex("5465737420436f6e666967"));
1492 QVERIFY(info->contains(chars[1].descriptors().at(0)));
1493 } else if (info->serviceUuid() ==
1494 QBluetoothUuid(QString("f000ccc0-0451-4000-b000-000000000000"))) {
1495 qDebug() << "Connection Control Service";
1496 QList<QLowEnergyCharacteristic> chars = info->characteristics();
1497 QCOMPARE(chars.count(), 3);
1498
1499 //first characteristic
1500 QString temp("f000ccc1-0451-4000-b000-000000000000");
1501 QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
1502 HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x77));
1503 QCOMPARE(chars[0].properties(),
1504 (QLowEnergyCharacteristic::Notify|QLowEnergyCharacteristic::Read));
1505 // the connection control parameter change from platform to platform
1506 // better not test them here
1507 //QCOMPARE(chars[0].value(), QByteArray::fromHex("000000000000"));
1508 QVERIFY(chars[0].isValid());
1509 QVERIFY(info->contains(chars[0]));
1510
1511 QCOMPARE(chars[0].descriptors().count(), 2);
1512 //descriptor checks
1513 QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
1514 HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x78));
1515 QCOMPARE(chars[0].descriptors().at(0).uuid(),
1516 QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
1517 QCOMPARE(chars[0].descriptors().at(0).type(),
1518 QBluetoothUuid::ClientCharacteristicConfiguration);
1519 QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
1520 QVERIFY(info->contains(chars[0].descriptors().at(0)));
1521
1522 QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
1523 // value different in other revisions and test may fail
1524 HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x79));
1525 QCOMPARE(chars[0].descriptors().at(1).uuid(),
1526 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1527 QCOMPARE(chars[0].descriptors().at(1).type(),
1528 QBluetoothUuid::CharacteristicUserDescription);
1529 QCOMPARE(chars[0].descriptors().at(1).value(),
1530 QByteArray::fromHex("436f6e6e2e20506172616d73"));
1531 QVERIFY(info->contains(chars[0].descriptors().at(1)));
1532
1533 //second characteristic
1534 temp = QString("f000ccc2-0451-4000-b000-000000000000");
1535 QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
1536 HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x7b));
1537 QCOMPARE(chars[1].properties(), QLowEnergyCharacteristic::Write);
1538 QCOMPARE(chars[1].value(), QByteArray());
1539 QVERIFY(chars[1].isValid());
1540 QVERIFY(info->contains(chars[1]));
1541
1542 QCOMPARE(chars[1].descriptors().count(), 1);
1543 QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
1544 HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x7c));
1545 QCOMPARE(chars[1].descriptors().at(0).uuid(),
1546 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1547 QCOMPARE(chars[1].descriptors().at(0).type(),
1548 QBluetoothUuid::CharacteristicUserDescription);
1549 QCOMPARE(chars[1].descriptors().at(0).value(),
1550 QByteArray::fromHex("436f6e6e2e20506172616d7320526571"));
1551 QVERIFY(info->contains(chars[1].descriptors().at(0)));
1552
1553 //third characteristic
1554 temp = QString("f000ccc3-0451-4000-b000-000000000000");
1555 QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp));
1556 HANDLE_COMPARE(chars[2].handle(), QLowEnergyHandle(0x7e));
1557 QCOMPARE(chars[2].properties(), QLowEnergyCharacteristic::Write);
1558 QCOMPARE(chars[2].value(), QByteArray());
1559 QVERIFY(chars[2].isValid());
1560 QVERIFY(info->contains(chars[2]));
1561
1562 QCOMPARE(chars[2].descriptors().count(), 1);
1563 QCOMPARE(chars[2].descriptors().at(0).isValid(), true);
1564 HANDLE_COMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x7f));
1565 QCOMPARE(chars[2].descriptors().at(0).uuid(),
1566 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1567 QCOMPARE(chars[2].descriptors().at(0).type(),
1568 QBluetoothUuid::CharacteristicUserDescription);
1569 QCOMPARE(chars[2].descriptors().at(0).value(),
1570 QByteArray::fromHex("446973636f6e6e65637420526571"));
1571 QVERIFY(info->contains(chars[2].descriptors().at(0)));
1572 } else if (info->serviceUuid() ==
1573 QBluetoothUuid(QString("f000ffc0-0451-4000-b000-000000000000"))) {
1574 qDebug() << "Verifying OID Service";
1575 QList<QLowEnergyCharacteristic> chars = info->characteristics();
1576 QCOMPARE(chars.count(), 2);
1577
1578 // first characteristic
1579 QString temp("f000ffc1-0451-4000-b000-000000000000");
1580 QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp));
1581 // value different in other revisions and test may fail
1582 HANDLE_COMPARE(chars[0].handle(), QLowEnergyHandle(0x82));
1583 QCOMPARE(chars[0].properties(),
1584 (QLowEnergyCharacteristic::Notify|QLowEnergyCharacteristic::Write|QLowEnergyCharacteristic::WriteNoResponse));
1585 QCOMPARE(chars[0].value(), QByteArray());
1586 QVERIFY(chars[0].isValid());
1587 QVERIFY(info->contains(chars[0]));
1588
1589 QCOMPARE(chars[0].descriptors().count(), 2);
1590 //descriptor checks
1591 QCOMPARE(chars[0].descriptors().at(0).isValid(), true);
1592 HANDLE_COMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x83));
1593 QCOMPARE(chars[0].descriptors().at(0).uuid(),
1594 QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
1595 QCOMPARE(chars[0].descriptors().at(0).type(),
1596 QBluetoothUuid::ClientCharacteristicConfiguration);
1597 QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
1598 QVERIFY(info->contains(chars[0].descriptors().at(0)));
1599
1600 QCOMPARE(chars[0].descriptors().at(1).isValid(), true);
1601 // value different in other revisions and test may fail
1602 HANDLE_COMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x84));
1603 QCOMPARE(chars[0].descriptors().at(1).uuid(),
1604 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1605 QCOMPARE(chars[0].descriptors().at(1).type(),
1606 QBluetoothUuid::CharacteristicUserDescription);
1607 QCOMPARE(chars[0].descriptors().at(1).value(),
1608 QByteArray::fromHex("496d67204964656e74696679"));
1609 QVERIFY(info->contains(chars[0].descriptors().at(1)));
1610
1611 // second characteristic
1612 temp = QString("f000ffc2-0451-4000-b000-000000000000");
1613 QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp));
1614 // value different in other revisions and test may fail
1615 HANDLE_COMPARE(chars[1].handle(), QLowEnergyHandle(0x86));
1616 QCOMPARE(chars[1].properties(),
1617 (QLowEnergyCharacteristic::Notify|QLowEnergyCharacteristic::Write|QLowEnergyCharacteristic::WriteNoResponse));
1618 QCOMPARE(chars[1].value(), QByteArray());
1619 QVERIFY(chars[1].isValid());
1620 QVERIFY(info->contains(chars[1]));
1621
1622 QCOMPARE(chars[1].descriptors().count(), 2);
1623 //descriptor checks
1624 QCOMPARE(chars[1].descriptors().at(0).isValid(), true);
1625 // value different in other revisions and test may fail
1626 HANDLE_COMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x87));
1627 QCOMPARE(chars[1].descriptors().at(0).uuid(),
1628 QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
1629 QCOMPARE(chars[1].descriptors().at(0).type(),
1630 QBluetoothUuid::ClientCharacteristicConfiguration);
1631 QVERIFY(verifyClientCharacteristicValue(chars[0].descriptors().at(0).value()));
1632 QVERIFY(info->contains(chars[1].descriptors().at(0)));
1633
1634 QCOMPARE(chars[1].descriptors().at(1).isValid(), true);
1635 // value different in other revisions and test may fail
1636 HANDLE_COMPARE(chars[1].descriptors().at(1).handle(), QLowEnergyHandle(0x88));
1637 QCOMPARE(chars[1].descriptors().at(1).uuid(),
1638 QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription));
1639 QCOMPARE(chars[1].descriptors().at(1).type(),
1640 QBluetoothUuid::CharacteristicUserDescription);
1641 QCOMPARE(chars[1].descriptors().at(1).value(),
1642 QByteArray::fromHex("496d6720426c6f636b"));
1643 QVERIFY(info->contains(chars[1].descriptors().at(1)));
1644 } else {
1645 QFAIL(QString("Service not found" + info->serviceUuid().toString()).toUtf8().constData());
1646 }
1647}
1648
1649/*
1650 * CCC descriptors can have one of three distinct values:
1651 * 0000 - notifications and indications are off
1652 * 0100 - notifications enabled
1653 * 0200 - indications enabled
1654 *
1655 * The exact value is managed by the BTLE peripheral for each central
1656 * that connects. The value of this field is session based and may be retained
1657 * during multiple connections.
1658 *
1659 * This function returns \c true if the CCC value has a valid range.
1660 * */
1661bool tst_QLowEnergyController::verifyClientCharacteristicValue(const QByteArray &value)
1662{
1663 if (value == QByteArray::fromHex(hexEncoded: "0000")
1664 || value == QByteArray::fromHex(hexEncoded: "0100")
1665 || value == QByteArray::fromHex(hexEncoded: "0200") )
1666 return true;
1667
1668 qWarning() << "Found incorrect CC value" << value.toHex();
1669 return false;
1670}
1671
1672void tst_QLowEnergyController::tst_defaultBehavior()
1673{
1674 QList<QBluetoothAddress> foundAddresses;
1675 const QList<QBluetoothHostInfo> infos = QBluetoothLocalDevice::allDevices();
1676 for (const QBluetoothHostInfo &info : infos)
1677 foundAddresses.append(t: info.address());
1678 const QBluetoothAddress randomAddress("11:22:33:44:55:66");
1679
1680 // Test automatic detection of local adapter
1681 QLowEnergyController controlDefaultAdapter(randomAddress);
1682 QCOMPARE(controlDefaultAdapter.remoteAddress(), randomAddress);
1683 QCOMPARE(controlDefaultAdapter.state(), QLowEnergyController::UnconnectedState);
1684 if (foundAddresses.isEmpty()) {
1685 QVERIFY(controlDefaultAdapter.localAddress().isNull());
1686 } else {
1687 QCOMPARE(controlDefaultAdapter.error(), QLowEnergyController::NoError);
1688 QVERIFY(controlDefaultAdapter.errorString().isEmpty());
1689 QVERIFY(foundAddresses.contains(controlDefaultAdapter.localAddress()));
1690
1691 // unrelated uuids don't return valid service object
1692 // invalid service uuid
1693 QVERIFY(!controlDefaultAdapter.createServiceObject(
1694 QBluetoothUuid()));
1695 // some random uuid
1696 QVERIFY(!controlDefaultAdapter.createServiceObject(
1697 QBluetoothUuid(QBluetoothUuid::DeviceName)));
1698 }
1699
1700 QCOMPARE(controlDefaultAdapter.services().count(), 0);
1701
1702 // Test explicit local adapter
1703 if (!foundAddresses.isEmpty()) {
1704 QLowEnergyController controlExplicitAdapter(randomAddress,
1705 foundAddresses[0]);
1706 QCOMPARE(controlExplicitAdapter.remoteAddress(), randomAddress);
1707 QCOMPARE(controlExplicitAdapter.localAddress(), foundAddresses[0]);
1708 QCOMPARE(controlExplicitAdapter.state(),
1709 QLowEnergyController::UnconnectedState);
1710 QCOMPARE(controlExplicitAdapter.services().count(), 0);
1711
1712 // unrelated uuids don't return valid service object
1713 // invalid service uuid
1714 QVERIFY(!controlExplicitAdapter.createServiceObject(
1715 QBluetoothUuid()));
1716 // some random uuid
1717 QVERIFY(!controlExplicitAdapter.createServiceObject(
1718 QBluetoothUuid(QBluetoothUuid::DeviceName)));
1719 }
1720}
1721
1722void tst_QLowEnergyController::tst_writeCharacteristic()
1723{
1724#if !defined(Q_OS_MACOS) && !QT_CONFIG(winrt_bt)
1725 QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
1726 if (localAdapters.isEmpty())
1727 QSKIP("No local Bluetooth device found. Skipping test.");
1728#endif
1729
1730 if (!remoteDeviceInfo.isValid())
1731 QSKIP("No remote BTLE device found. Skipping test.");
1732 QLowEnergyController control(remoteDeviceInfo);
1733
1734 QCOMPARE(control.error(), QLowEnergyController::NoError);
1735
1736 control.connectToDevice();
1737 {
1738 QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
1739 30000);
1740 }
1741
1742 if (control.state() == QLowEnergyController::ConnectingState
1743 || control.error() != QLowEnergyController::NoError) {
1744 // default BTLE backend forever hangs in ConnectingState
1745 QSKIP("Cannot connect to remote device");
1746 }
1747
1748 QTRY_VERIFY_WITH_TIMEOUT(control.state() == QLowEnergyController::ConnectedState, 20000);
1749 QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
1750 QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
1751 control.discoverServices();
1752 QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
1753 QCOMPARE(stateSpy.count(), 2);
1754 QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
1755 QLowEnergyController::DiscoveringState);
1756 QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
1757 QLowEnergyController::DiscoveredState);
1758
1759 const QBluetoothUuid testService(QString("f000aa60-0451-4000-b000-000000000000"));
1760 QList<QBluetoothUuid> uuids = control.services();
1761 QVERIFY(uuids.contains(testService));
1762
1763 QLowEnergyService *service = control.createServiceObject(service: testService, parent: this);
1764 QVERIFY(service);
1765 service->discoverDetails();
1766 QTRY_VERIFY_WITH_TIMEOUT(
1767 service->state() == QLowEnergyService::ServiceDiscovered, 30000);
1768
1769 // test service described by
1770 // http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User%27s_Guide
1771 const QList<QLowEnergyCharacteristic> chars = service->characteristics();
1772
1773 QLowEnergyCharacteristic dataChar;
1774 QLowEnergyCharacteristic configChar;
1775 for (int i = 0; i < chars.count(); i++) {
1776 if (chars[i].uuid() == QBluetoothUuid(QString("f000aa61-0451-4000-b000-000000000000")))
1777 dataChar = chars[i];
1778 else if (chars[i].uuid() == QBluetoothUuid(QString("f000aa62-0451-4000-b000-000000000000")))
1779 configChar = chars[i];
1780 }
1781
1782 QVERIFY(dataChar.isValid());
1783 QVERIFY(!(dataChar.properties() & ~QLowEnergyCharacteristic::Read)); // only a read char
1784 QVERIFY(service->contains(dataChar));
1785 QVERIFY(configChar.isValid());
1786 QVERIFY(configChar.properties() & QLowEnergyCharacteristic::Write);
1787 QVERIFY(configChar.properties() & QLowEnergyCharacteristic::Read);
1788 QVERIFY(service->contains(configChar));
1789
1790 QCOMPARE(dataChar.value(), QByteArray::fromHex("3f00"));
1791 QVERIFY(configChar.value() == QByteArray::fromHex("00")
1792 || configChar.value() == QByteArray::fromHex("81"));
1793
1794 QSignalSpy writeSpy(service,
1795 SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)));
1796 QSignalSpy readSpy(service,
1797 SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)));
1798
1799 // *******************************************
1800 // test writing of characteristic
1801 // enable Blinking LED if not already enabled
1802 if (configChar.value() != QByteArray::fromHex(hexEncoded: "81")) {
1803 service->writeCharacteristic(characteristic: configChar, newValue: QByteArray::fromHex(hexEncoded: "81")); //0x81 blink LED D1
1804 QTRY_VERIFY_WITH_TIMEOUT(!writeSpy.isEmpty(), 10000);
1805 QCOMPARE(configChar.value(), QByteArray::fromHex("81"));
1806 QList<QVariant> firstSignalData = writeSpy.first();
1807 QLowEnergyCharacteristic signalChar = firstSignalData[0].value<QLowEnergyCharacteristic>();
1808 QByteArray signalValue = firstSignalData[1].toByteArray();
1809
1810 QCOMPARE(signalValue, QByteArray::fromHex("81"));
1811 QVERIFY(signalChar == configChar);
1812
1813 writeSpy.clear();
1814
1815 }
1816
1817 // test direct read of configChar
1818 QVERIFY(readSpy.isEmpty());
1819 service->readCharacteristic(characteristic: configChar);
1820 QTRY_VERIFY_WITH_TIMEOUT(!readSpy.isEmpty(), 10000);
1821 QCOMPARE(configChar.value(), QByteArray::fromHex("81"));
1822 QCOMPARE(readSpy.count(), 1); //expect one characteristicRead signal
1823 {
1824 //verify the readCharacteristic()
1825 QList<QVariant> firstSignalData = readSpy.first();
1826 QLowEnergyCharacteristic signalChar = firstSignalData[0].value<QLowEnergyCharacteristic>();
1827 QByteArray signalValue = firstSignalData[1].toByteArray();
1828
1829 QCOMPARE(signalValue, QByteArray::fromHex("81"));
1830 QCOMPARE(signalValue, configChar.value());
1831 QVERIFY(signalChar == configChar);
1832 }
1833
1834 service->writeCharacteristic(characteristic: configChar, newValue: QByteArray::fromHex(hexEncoded: "00")); //turn LED D1 off
1835 QTRY_VERIFY_WITH_TIMEOUT(!writeSpy.isEmpty(), 10000);
1836 QCOMPARE(configChar.value(), QByteArray::fromHex("00"));
1837 QList<QVariant> firstSignalData = writeSpy.first();
1838 QLowEnergyCharacteristic signalChar = firstSignalData[0].value<QLowEnergyCharacteristic>();
1839 QByteArray signalValue = firstSignalData[1].toByteArray();
1840
1841 QCOMPARE(signalValue, QByteArray::fromHex("00"));
1842 QVERIFY(signalChar == configChar);
1843
1844 // *******************************************
1845 // write wrong value -> error response required
1846 QSignalSpy errorSpy(service, SIGNAL(error(QLowEnergyService::ServiceError)));
1847 writeSpy.clear();
1848 QCOMPARE(errorSpy.count(), 0);
1849 QCOMPARE(writeSpy.count(), 0);
1850
1851 // write 2 byte value to 1 byte characteristic
1852 service->writeCharacteristic(characteristic: configChar, newValue: QByteArray::fromHex(hexEncoded: "1111"));
1853 QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 10000);
1854 QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
1855 QLowEnergyService::CharacteristicWriteError);
1856 QCOMPARE(service->error(), QLowEnergyService::CharacteristicWriteError);
1857 QCOMPARE(writeSpy.count(), 0);
1858 QCOMPARE(configChar.value(), QByteArray::fromHex("00"));
1859
1860 // *******************************************
1861 // write to read-only characteristic -> error
1862 errorSpy.clear();
1863 QCOMPARE(errorSpy.count(), 0);
1864 service->writeCharacteristic(characteristic: dataChar, newValue: QByteArray::fromHex(hexEncoded: "ffff"));
1865
1866 QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 10000);
1867 QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
1868 QLowEnergyService::CharacteristicWriteError);
1869 QCOMPARE(service->error(), QLowEnergyService::CharacteristicWriteError);
1870 QCOMPARE(writeSpy.count(), 0);
1871 QCOMPARE(dataChar.value(), QByteArray::fromHex("3f00"));
1872
1873
1874 control.disconnectFromDevice();
1875
1876 QTRY_COMPARE(control.state(), QLowEnergyController::UnconnectedState);
1877 QCOMPARE(control.error(), QLowEnergyController::NoError);
1878 // *******************************************
1879 // write value while disconnected -> error
1880 errorSpy.clear();
1881 QCOMPARE(errorSpy.count(), 0);
1882 service->writeCharacteristic(characteristic: configChar, newValue: QByteArray::fromHex(hexEncoded: "ffff"));
1883 QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 2000);
1884 QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
1885 QLowEnergyService::OperationError);
1886 QCOMPARE(service->error(), QLowEnergyService::OperationError);
1887 QCOMPARE(writeSpy.count(), 0);
1888 QCOMPARE(configChar.value(), QByteArray::fromHex("00"));
1889
1890 // invalid characteristics still belong to their respective service
1891 QVERIFY(service->contains(configChar));
1892 QVERIFY(service->contains(dataChar));
1893
1894 QVERIFY(!service->contains(QLowEnergyCharacteristic()));
1895
1896 delete service;
1897}
1898
1899void tst_QLowEnergyController::tst_readWriteDescriptor()
1900{
1901#if !defined(Q_OS_MACOS) && !QT_CONFIG(winrt_bt)
1902 QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
1903 if (localAdapters.isEmpty())
1904 QSKIP("No local Bluetooth device found. Skipping test.");
1905#endif
1906
1907 if (!remoteDeviceInfo.isValid())
1908 QSKIP("No remote BTLE device found. Skipping test.");
1909 QLowEnergyController control(remoteDeviceInfo);
1910
1911 // quick setup - more elaborate test is done by connect()
1912 control.connectToDevice();
1913 {
1914 QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
1915 30000);
1916 }
1917
1918 if (control.state() == QLowEnergyController::ConnectingState
1919 || control.error() != QLowEnergyController::NoError) {
1920 // default BTLE backend forever hangs in ConnectingState
1921 QSKIP("Cannot connect to remote device");
1922 }
1923
1924 QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
1925 QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
1926 QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
1927 control.discoverServices();
1928 QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
1929 QCOMPARE(stateSpy.count(), 2);
1930 QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
1931 QLowEnergyController::DiscoveringState);
1932 QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
1933 QLowEnergyController::DiscoveredState);
1934
1935 const QBluetoothUuid testService(QString("f000aa00-0451-4000-b000-000000000000"));
1936 QList<QBluetoothUuid> uuids = control.services();
1937 QVERIFY(uuids.contains(testService));
1938
1939 QLowEnergyService *service = control.createServiceObject(service: testService, parent: this);
1940 QVERIFY(service);
1941 service->discoverDetails();
1942 QTRY_VERIFY_WITH_TIMEOUT(
1943 service->state() == QLowEnergyService::ServiceDiscovered, 30000);
1944
1945 // Temperature service described by
1946 // http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User%27s_Guide
1947
1948 // 1. Find temperature data characteristic
1949 const QLowEnergyCharacteristic tempData = service->characteristic(
1950 uuid: QBluetoothUuid(QStringLiteral("f000aa01-0451-4000-b000-000000000000")));
1951 const QLowEnergyCharacteristic tempConfig = service->characteristic(
1952 uuid: QBluetoothUuid(QStringLiteral("f000aa02-0451-4000-b000-000000000000")));
1953
1954 if (!tempData.isValid()) {
1955 delete service;
1956 control.disconnectFromDevice();
1957 QTRY_COMPARE(control.state(), QLowEnergyController::UnconnectedState);
1958 QCOMPARE(control.error(), QLowEnergyController::NoError);
1959 QSKIP("Cannot find temperature data characteristic of TI Sensor");
1960 }
1961
1962 // 2. Find temperature data notification descriptor
1963 const QLowEnergyDescriptor notification = tempData.descriptor(
1964 uuid: QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
1965
1966 if (!notification.isValid()) {
1967 delete service;
1968 control.disconnectFromDevice();
1969 QTRY_COMPARE(control.state(), QLowEnergyController::UnconnectedState);
1970 QCOMPARE(control.error(), QLowEnergyController::NoError);
1971 QSKIP("Cannot find temperature data notification of TI Sensor");
1972 }
1973
1974 QCOMPARE(notification.value(), QByteArray::fromHex("0000"));
1975 QVERIFY(service->contains(notification));
1976 QVERIFY(service->contains(tempData));
1977 if (tempConfig.isValid()) {
1978 QVERIFY(service->contains(tempConfig));
1979 QCOMPARE(tempConfig.value(), QByteArray::fromHex("00"));
1980 }
1981
1982 // 3. Test reading and writing to descriptor -> activate notifications
1983 QSignalSpy descWrittenSpy(service,
1984 SIGNAL(descriptorWritten(QLowEnergyDescriptor,QByteArray)));
1985 QSignalSpy descReadSpy(service,
1986 SIGNAL(descriptorRead(QLowEnergyDescriptor,QByteArray)));
1987 QSignalSpy charWrittenSpy(service,
1988 SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)));
1989 QSignalSpy charChangedSpy(service,
1990 SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)));
1991
1992 QLowEnergyDescriptor signalDesc;
1993 QList<QVariant> firstSignalData;
1994 QByteArray signalValue;
1995 if (notification.value() != QByteArray::fromHex(hexEncoded: "0100")) {
1996 // enable notifications if not already done
1997 service->writeDescriptor(descriptor: notification, newValue: QByteArray::fromHex(hexEncoded: "0100"));
1998
1999 QTRY_VERIFY_WITH_TIMEOUT(!descWrittenSpy.isEmpty(), 3000);
2000 QCOMPARE(notification.value(), QByteArray::fromHex("0100"));
2001 firstSignalData = descWrittenSpy.first();
2002 signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>();
2003 signalValue = firstSignalData[1].toByteArray();
2004 QCOMPARE(signalValue, QByteArray::fromHex("0100"));
2005 QVERIFY(notification == signalDesc);
2006 descWrittenSpy.clear();
2007 }
2008
2009 // 4. Test reception of notifications
2010 // activate the temperature sensor if available
2011 if (tempConfig.isValid()) {
2012 service->writeCharacteristic(characteristic: tempConfig, newValue: QByteArray::fromHex(hexEncoded: "01"));
2013
2014 // first signal is confirmation of tempConfig write
2015 // subsequent signals are temp data updates
2016 QTRY_VERIFY_WITH_TIMEOUT(charWrittenSpy.count() == 1, 10000);
2017 QTRY_VERIFY_WITH_TIMEOUT(charChangedSpy.count() >= 4, 10000);
2018
2019 QCOMPARE(charWrittenSpy.count(), 1);
2020 QLowEnergyCharacteristic writtenChar = charWrittenSpy[0].at(i: 0).value<QLowEnergyCharacteristic>();
2021 QByteArray writtenValue = charWrittenSpy[0].at(i: 1).toByteArray();
2022 QCOMPARE(tempConfig, writtenChar);
2023 QCOMPARE(tempConfig.value(), writtenValue);
2024 QCOMPARE(writtenChar.value(), writtenValue);
2025 QCOMPARE(writtenValue, QByteArray::fromHex("01"));
2026
2027 QList<QVariant> entry;
2028 for (int i = 0; i < charChangedSpy.count(); i++) {
2029 entry = charChangedSpy[i];
2030 const QLowEnergyCharacteristic ch = entry[0].value<QLowEnergyCharacteristic>();
2031
2032 QCOMPARE(tempData, ch);
2033
2034 //check last characteristic changed value matches the characteristics current value
2035 if (i == (charChangedSpy.count() - 1)) {
2036 writtenValue = entry[1].toByteArray();
2037 QCOMPARE(ch.value(), writtenValue);
2038 QCOMPARE(tempData.value(), writtenValue);
2039 }
2040 }
2041
2042 service->writeCharacteristic(characteristic: tempConfig, newValue: QByteArray::fromHex(hexEncoded: "00"));
2043 }
2044
2045 // 5. Test reading and writing of/to descriptor -> deactivate notifications
2046
2047 service->readDescriptor(descriptor: notification);
2048 QTRY_VERIFY_WITH_TIMEOUT(!descReadSpy.isEmpty(), 3000);
2049 QCOMPARE(descReadSpy.count(), 1);
2050 firstSignalData = descReadSpy.first();
2051 signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>();
2052 signalValue = firstSignalData[1].toByteArray();
2053 QCOMPARE(signalValue, notification.value());
2054 QCOMPARE(notification.value(), QByteArray::fromHex("0100"));
2055 descReadSpy.clear();
2056
2057
2058 service->writeDescriptor(descriptor: notification, newValue: QByteArray::fromHex(hexEncoded: "0000"));
2059 // verify
2060 QTRY_VERIFY_WITH_TIMEOUT(!descWrittenSpy.isEmpty(), 3000);
2061 QCOMPARE(notification.value(), QByteArray::fromHex("0000"));
2062 firstSignalData = descWrittenSpy.first();
2063 signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>();
2064 signalValue = firstSignalData[1].toByteArray();
2065 QCOMPARE(signalValue, QByteArray::fromHex("0000"));
2066 QVERIFY(notification == signalDesc);
2067 descWrittenSpy.clear();
2068
2069 // The series of wait calls below is required because toggling CCC via the notifying
2070 // property consistently crashes BlueZ 5.47. BlueZ 5.48 does not crash but
2071 // an error is thrown. For details see QTBUG-65729
2072 if (isBluezDbusLE)
2073 QTest::qWait(ms: 1000);
2074
2075 // test concurrent writeRequests
2076 // they need to be queued up
2077 service->writeDescriptor(descriptor: notification,newValue: QByteArray::fromHex(hexEncoded: "0100"));
2078 if (isBluezDbusLE)
2079 QTest::qWait(ms: 1000);
2080
2081 service->writeDescriptor(descriptor: notification, newValue: QByteArray::fromHex(hexEncoded: "0000"));
2082 if (isBluezDbusLE)
2083 QTest::qWait(ms: 1000);
2084
2085 service->writeDescriptor(descriptor: notification, newValue: QByteArray::fromHex(hexEncoded: "0100"));
2086 if (isBluezDbusLE)
2087 QTest::qWait(ms: 1000);
2088
2089 service->writeDescriptor(descriptor: notification, newValue: QByteArray::fromHex(hexEncoded: "0000"));
2090 if (isBluezDbusLE)
2091 QTest::qWait(ms: 1000);
2092
2093 QTRY_VERIFY_WITH_TIMEOUT(descWrittenSpy.count() == 4, 10000);
2094
2095 QCOMPARE(notification.value(), QByteArray::fromHex("0000"));
2096 for (int i = 0; i < descWrittenSpy.count(); i++) {
2097 firstSignalData = descWrittenSpy.at(i);
2098 signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>();
2099 signalValue = firstSignalData[1].toByteArray();
2100 if (i & 0x1) // odd
2101 QCOMPARE(signalValue, QByteArray::fromHex("0000"));
2102 else // even
2103 QCOMPARE(signalValue, QByteArray::fromHex("0100"));
2104 QVERIFY(notification == signalDesc);
2105
2106 }
2107
2108 // 5. Test reading and writing of/to descriptor -> deactivate notifications
2109
2110 service->readDescriptor(descriptor: notification);
2111 QTRY_VERIFY_WITH_TIMEOUT(!descReadSpy.isEmpty(), 3000);
2112 QCOMPARE(descReadSpy.count(), 1);
2113 firstSignalData = descReadSpy.first();
2114 signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>();
2115 signalValue = firstSignalData[1].toByteArray();
2116 QCOMPARE(signalValue, notification.value());
2117 QCOMPARE(notification.value(), QByteArray::fromHex("0000"));
2118 descReadSpy.clear();
2119
2120 descWrittenSpy.clear();
2121
2122 // *******************************************
2123 // write wrong value -> error response required
2124 QSignalSpy errorSpy(service, SIGNAL(error(QLowEnergyService::ServiceError)));
2125 descWrittenSpy.clear();
2126 QCOMPARE(errorSpy.count(), 0);
2127 QCOMPARE(descWrittenSpy.count(), 0);
2128
2129 // write 4 byte value to 2 byte characteristic
2130 service->writeDescriptor(descriptor: notification, newValue: QByteArray::fromHex(hexEncoded: "11112222"));
2131#ifdef Q_OS_MAC
2132 // On OS X/iOS we have a special method to set notify value,
2133 // it accepts only false/true and not
2134 // writing descriptors, there is only one way to find this error -
2135 // immediately intercept in LE controller and set the error.
2136 QVERIFY(!errorSpy.isEmpty());
2137#else
2138 QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 30000);
2139#endif
2140 QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
2141 QLowEnergyService::DescriptorWriteError);
2142 QCOMPARE(service->error(), QLowEnergyService::DescriptorWriteError);
2143 QCOMPARE(descWrittenSpy.count(), 0);
2144 QCOMPARE(notification.value(), QByteArray::fromHex("0000"));
2145
2146 control.disconnectFromDevice();
2147 QTRY_COMPARE(control.state(), QLowEnergyController::UnconnectedState);
2148 QCOMPARE(control.error(), QLowEnergyController::NoError);
2149
2150 // *******************************************
2151 // write value while disconnected -> error
2152 errorSpy.clear();
2153 service->writeDescriptor(descriptor: notification, newValue: QByteArray::fromHex(hexEncoded: "0100"));
2154 QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 2000);
2155 QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
2156 QLowEnergyService::OperationError);
2157 QCOMPARE(service->error(), QLowEnergyService::OperationError);
2158 QCOMPARE(descWrittenSpy.count(), 0);
2159 QCOMPARE(notification.value(), QByteArray::fromHex("0000"));
2160
2161 delete service;
2162}
2163
2164/*
2165 * By default this test is skipped.
2166 *
2167 * Following tests are performed:
2168 * - encrypted read and discovery
2169 * - readCharacteristic() of values longer than MTU
2170 * - readCharacteristic() if values equal to MTU
2171 *
2172 * This test is semi manual as the test device environment is very specific.
2173 * A programmable BTLE device is required. Currently, the test requires
2174 * the CSR Dev Kit using the hr_sensor example.
2175 *
2176 * The following changes must be done to example to be able to fully
2177 * utilise the test:
2178 * 1.) gap_service_db.db -> UUID_DEVICE_NAME char - add FLAG_ENCR_R
2179 * => tests encrypted read/discovery
2180 * 2.) dev_info_service_db.db -> UUID_DEVICE_INFO_MANUFACTURER_NAME
2181 * => The default name "Cambridge Silicon Radio" must be changed
2182 * to "Cambridge Silicon Radi" (new length 22)
2183 * 3.) revert change 1 above and redo test. This attempts to write a
2184 * char that is readable w/o encryption but writeable with encryption
2185 * => tests encryption code lines in writeCharacteristic()
2186 * => otherwise the read encryption would have increased security level already
2187 * => programmable CSR device must be reset before each run of this test
2188 * (to undo the previous write)
2189 */
2190void tst_QLowEnergyController::tst_customProgrammableDevice()
2191{
2192 QSKIP("Skipping encryption");
2193
2194 //Adjust the uuids and device address as see fit to match
2195 //values that match the current test environment
2196 //The target characteristic must be readble and writable
2197 //under encryption to test dynamic switching of security level
2198 QBluetoothAddress encryptedDevice(QString("00:02:5B:00:15:10"));
2199 QBluetoothUuid serviceUuid(QBluetoothUuid::GenericAccess);
2200 QBluetoothUuid characterristicUuid(QBluetoothUuid::DeviceName);
2201
2202 QLowEnergyController control(encryptedDevice);
2203 QCOMPARE(control.error(), QLowEnergyController::NoError);
2204
2205 control.connectToDevice();
2206 {
2207 QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
2208 30000);
2209 }
2210
2211 if (control.state() == QLowEnergyController::ConnectingState
2212 || control.error() != QLowEnergyController::NoError) {
2213 // default BTLE backend forever hangs in ConnectingState
2214 QSKIP("Cannot connect to remote device");
2215 }
2216
2217 QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
2218 QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
2219 QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
2220 control.discoverServices();
2221 QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
2222 QCOMPARE(stateSpy.count(), 2);
2223 QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
2224 QLowEnergyController::DiscoveringState);
2225 QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
2226 QLowEnergyController::DiscoveredState);
2227
2228 QList<QBluetoothUuid> uuids = control.services();
2229 QVERIFY(uuids.contains(serviceUuid));
2230
2231 QLowEnergyService *service = control.createServiceObject(service: serviceUuid, parent: this);
2232 QVERIFY(service);
2233
2234 // 1.) discovery triggers read of device name char which is encrypted
2235 service->discoverDetails();
2236 QTRY_VERIFY_WITH_TIMEOUT(
2237 service->state() == QLowEnergyService::ServiceDiscovered, 30000);
2238
2239 QLowEnergyCharacteristic encryptedChar = service->characteristic(
2240 uuid: characterristicUuid);
2241 const QByteArray encryptedReference("CSR HR Sensor");
2242 QVERIFY(encryptedChar.isValid());
2243 QCOMPARE(encryptedChar.value(), encryptedReference);
2244
2245 // 2.) read of encrypted characteristic
2246 // => the discovery of the encrypted char above will have switched to
2247 // encryption already.
2248 QSignalSpy encryptedReadSpy(service,
2249 SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)));
2250 QSignalSpy encryptedErrorSpy(service,
2251 SIGNAL(error(QLowEnergyService::ServiceError)));
2252 service->readCharacteristic(characteristic: encryptedChar);
2253 QTRY_VERIFY_WITH_TIMEOUT(!encryptedReadSpy.isEmpty(), 10000);
2254 QVERIFY(encryptedErrorSpy.isEmpty());
2255 QCOMPARE(encryptedReadSpy.count(), 1);
2256 QList<QVariant> entry = encryptedReadSpy[0];
2257 QVERIFY(entry[0].value<QLowEnergyCharacteristic>() == encryptedChar);
2258 QCOMPARE(entry[1].toByteArray(), encryptedReference);
2259 QCOMPARE(encryptedChar.value(), encryptedReference);
2260
2261 // 3.) write to encrypted characteristic
2262 QSignalSpy encryptedWriteSpy(service,
2263 SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)));
2264 encryptedReadSpy.clear();
2265 encryptedErrorSpy.clear();
2266 const QByteArray newValue("ZZZ HR Sensor");
2267 service->writeCharacteristic(characteristic: encryptedChar, newValue);
2268 QTRY_VERIFY_WITH_TIMEOUT(!encryptedWriteSpy.isEmpty(), 10000);
2269 QVERIFY(encryptedErrorSpy.isEmpty());
2270 QVERIFY(encryptedReadSpy.isEmpty());
2271 QCOMPARE(encryptedWriteSpy.count(), 1);
2272 entry = encryptedWriteSpy[0];
2273 QVERIFY(entry[0].value<QLowEnergyCharacteristic>() == encryptedChar);
2274 QCOMPARE(entry[1].toByteArray(), newValue);
2275 QCOMPARE(encryptedChar.value(), newValue);
2276
2277 delete service;
2278
2279 //change to Device Information service
2280 QVERIFY(uuids.contains(QBluetoothUuid::DeviceInformation));
2281 service = control.createServiceObject(service: QBluetoothUuid::DeviceInformation);
2282 QVERIFY(service);
2283
2284 service->discoverDetails();
2285 QTRY_VERIFY_WITH_TIMEOUT(
2286 service->state() == QLowEnergyService::ServiceDiscovered, 30000);
2287
2288 // 4.) read of software revision string which is longer than mtu
2289 // tests readCharacteristic() including blob reads
2290 QSignalSpy readSpy(service,
2291 SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)));
2292 QSignalSpy errorSpy(service,
2293 SIGNAL(error(QLowEnergyService::ServiceError)));
2294
2295 const QByteArray expectedSoftRev("Application version 2.3.0.0");
2296 QLowEnergyCharacteristic softwareRevChar
2297 = service->characteristic(uuid: QBluetoothUuid::SoftwareRevisionString);
2298 QVERIFY(softwareRevChar.isValid());
2299 QCOMPARE(softwareRevChar.value(), expectedSoftRev);
2300
2301 service->readCharacteristic(characteristic: softwareRevChar);
2302 QTRY_VERIFY_WITH_TIMEOUT(!readSpy.isEmpty(), 10000);
2303 QVERIFY(errorSpy.isEmpty());
2304 QCOMPARE(readSpy.count(), 1);
2305 entry = readSpy[0];
2306 QVERIFY(entry[0].value<QLowEnergyCharacteristic>() == softwareRevChar);
2307 QCOMPARE(entry[1].toByteArray(), expectedSoftRev);
2308 QCOMPARE(softwareRevChar.value(), expectedSoftRev);
2309
2310
2311 // 5.) read of manufacturer string which is exactly as long as single
2312 // MTU size (assuming negotiated MTU is 23)
2313 // => blob read test without blob being required
2314 // => the read blob answer will have zero length
2315
2316 readSpy.clear();
2317
2318 // This assumes the manufacturer string was mondified via CSR SDK
2319 // see function description above
2320 const QByteArray expectedManufacturer("Cambridge Silicon Radi");
2321 QLowEnergyCharacteristic manufacturerChar = service->characteristic(
2322 uuid: QBluetoothUuid::ManufacturerNameString);
2323 QVERIFY(manufacturerChar.isValid());
2324 QCOMPARE(manufacturerChar.value(), expectedManufacturer);
2325
2326 service->readCharacteristic(characteristic: manufacturerChar);
2327 QTRY_VERIFY_WITH_TIMEOUT(!readSpy.isEmpty(), 10000);
2328 QVERIFY(errorSpy.isEmpty());
2329 QCOMPARE(readSpy.count(), 1);
2330 entry = readSpy[0];
2331 QVERIFY(entry[0].value<QLowEnergyCharacteristic>() == manufacturerChar);
2332 QCOMPARE(entry[1].toByteArray(), expectedManufacturer);
2333 QCOMPARE(manufacturerChar.value(), expectedManufacturer);
2334
2335 delete service;
2336 control.disconnectFromDevice();
2337 QTRY_COMPARE(control.state(), QLowEnergyController::UnconnectedState);
2338 QCOMPARE(control.error(), QLowEnergyController::NoError);
2339}
2340
2341
2342/* 1.) Test with undiscovered devices
2343 - read and write invalid char
2344 2.) Test with discovered devices
2345 - read non-readable char
2346 - write non-writable char
2347 */
2348void tst_QLowEnergyController::tst_errorCases()
2349{
2350#if !defined(Q_OS_MACOS) && !QT_CONFIG(winrt_bt)
2351 QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
2352 if (localAdapters.isEmpty())
2353 QSKIP("No local Bluetooth device found. Skipping test.");
2354#endif
2355
2356 if (!remoteDeviceInfo.isValid())
2357 QSKIP("No remote BTLE device found. Skipping test.");
2358 QLowEnergyController control(remoteDeviceInfo);
2359 QCOMPARE(control.error(), QLowEnergyController::NoError);
2360
2361 control.connectToDevice();
2362 {
2363 QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
2364 30000);
2365 }
2366
2367 if (control.state() == QLowEnergyController::ConnectingState
2368 || control.error() != QLowEnergyController::NoError) {
2369 // default BTLE backend forever hangs in ConnectingState
2370 QSKIP("Cannot connect to remote device");
2371 }
2372
2373 QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
2374 QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
2375 QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
2376 control.discoverServices();
2377 QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
2378 QCOMPARE(stateSpy.count(), 2);
2379 QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
2380 QLowEnergyController::DiscoveringState);
2381 QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
2382 QLowEnergyController::DiscoveredState);
2383
2384
2385 // Setup required uuids
2386 const QBluetoothUuid irTemperaturServiceUuid(QStringLiteral("f000aa00-0451-4000-b000-000000000000"));
2387 const QBluetoothUuid irCharUuid(QString("f000aa01-0451-4000-b000-000000000000"));
2388 const QBluetoothUuid oadServiceUuid(QStringLiteral("f000ffc0-0451-4000-b000-000000000000"));
2389 const QBluetoothUuid oadCharUuid(QString("f000ffc1-0451-4000-b000-000000000000"));
2390
2391 QVERIFY(control.services().contains(irTemperaturServiceUuid));
2392 QVERIFY(control.services().contains(oadServiceUuid));
2393
2394 // Create service objects and basic tests
2395 QLowEnergyService *irService = control.createServiceObject(service: irTemperaturServiceUuid);
2396 QVERIFY(irService);
2397 QCOMPARE(irService->state(), QLowEnergyService::DiscoveryRequired);
2398 QVERIFY(irService->characteristics().isEmpty());
2399 QLowEnergyService *oadService = control.createServiceObject(service: oadServiceUuid);
2400 QVERIFY(oadService);
2401 QCOMPARE(oadService->state(), QLowEnergyService::DiscoveryRequired);
2402 QVERIFY(oadService->characteristics().isEmpty());
2403
2404 QLowEnergyCharacteristic invalidChar;
2405 QLowEnergyDescriptor invalidDesc;
2406
2407 QVERIFY(!irService->contains(invalidChar));
2408 QVERIFY(!irService->contains(invalidDesc));
2409
2410 QSignalSpy irErrorSpy(irService, SIGNAL(error(QLowEnergyService::ServiceError)));
2411 QSignalSpy oadErrorSpy(oadService, SIGNAL(error(QLowEnergyService::ServiceError)));
2412
2413 QSignalSpy irReadSpy(irService, SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)));
2414 QSignalSpy irWrittenSpy(irService, SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)));
2415 QSignalSpy irDescReadSpy(irService, SIGNAL(descriptorRead(QLowEnergyDescriptor,QByteArray)));
2416 QSignalSpy irDescWrittenSpy(irService, SIGNAL(descriptorWritten(QLowEnergyDescriptor,QByteArray)));
2417
2418 QSignalSpy oadCharReadSpy(oadService, SIGNAL(descriptorRead(QLowEnergyDescriptor,QByteArray)));
2419
2420 // ********************************************************
2421 // Test read/write to discovered service
2422 // with invalid characteristic & descriptor
2423
2424 // discover IR Service
2425 irService->discoverDetails();
2426 QTRY_VERIFY_WITH_TIMEOUT(
2427 irService->state() == QLowEnergyService::ServiceDiscovered, 30000);
2428 QVERIFY(!irService->contains(invalidChar));
2429 QVERIFY(!irService->contains(invalidDesc));
2430 irErrorSpy.clear();
2431
2432 // read invalid characteristic
2433 irService->readCharacteristic(characteristic: invalidChar);
2434 QTRY_VERIFY_WITH_TIMEOUT(!irErrorSpy.isEmpty(), 5000);
2435 QCOMPARE(irErrorSpy.count(), 1);
2436 QVERIFY(irWrittenSpy.isEmpty());
2437 QVERIFY(irReadSpy.isEmpty());
2438 QCOMPARE(irErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
2439 QLowEnergyService::OperationError);
2440 irErrorSpy.clear();
2441
2442 // read invalid descriptor
2443 irService->readDescriptor(descriptor: invalidDesc);
2444 QTRY_VERIFY_WITH_TIMEOUT(!irErrorSpy.isEmpty(), 5000);
2445 QCOMPARE(irErrorSpy.count(), 1);
2446 QVERIFY(irDescWrittenSpy.isEmpty());
2447 QVERIFY(irDescReadSpy.isEmpty());
2448 QCOMPARE(irErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
2449 QLowEnergyService::OperationError);
2450 irErrorSpy.clear();
2451
2452 // write invalid characteristic
2453 irService->writeCharacteristic(characteristic: invalidChar, newValue: QByteArray("foo"));
2454 QTRY_VERIFY_WITH_TIMEOUT(!irErrorSpy.isEmpty(), 5000);
2455 QCOMPARE(irErrorSpy.count(), 1);
2456 QVERIFY(irWrittenSpy.isEmpty());
2457 QVERIFY(irReadSpy.isEmpty());
2458 QCOMPARE(irErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
2459 QLowEnergyService::OperationError);
2460 irErrorSpy.clear();
2461
2462 // write invalid descriptor
2463 irService->readDescriptor(descriptor: invalidDesc);
2464 QTRY_VERIFY_WITH_TIMEOUT(!irErrorSpy.isEmpty(), 5000);
2465 QCOMPARE(irErrorSpy.count(), 1);
2466 QVERIFY(irDescWrittenSpy.isEmpty());
2467 QVERIFY(irDescReadSpy.isEmpty());
2468 QCOMPARE(irErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
2469 QLowEnergyService::OperationError);
2470 irErrorSpy.clear();
2471
2472 // ********************************************************
2473 // Test read/write to undiscovered service
2474 // with invalid characteristic & descriptor
2475
2476 // read invalid characteristic
2477 oadService->readCharacteristic(characteristic: invalidChar);
2478 QTRY_VERIFY_WITH_TIMEOUT(!oadErrorSpy.isEmpty(), 5000);
2479 QCOMPARE(oadErrorSpy.count(), 1);
2480 QCOMPARE(oadErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
2481 QLowEnergyService::OperationError);
2482 oadErrorSpy.clear();
2483
2484 // read invalid descriptor
2485 oadService->readDescriptor(descriptor: invalidDesc);
2486 QTRY_VERIFY_WITH_TIMEOUT(!oadErrorSpy.isEmpty(), 5000);
2487 QCOMPARE(oadErrorSpy.count(), 1);
2488 QCOMPARE(oadErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
2489 QLowEnergyService::OperationError);
2490 oadErrorSpy.clear();
2491
2492 // write invalid characteristic
2493 oadService->writeCharacteristic(characteristic: invalidChar, newValue: QByteArray("foo"));
2494 QTRY_VERIFY_WITH_TIMEOUT(!oadErrorSpy.isEmpty(), 5000);
2495 QCOMPARE(oadErrorSpy.count(), 1);
2496 QCOMPARE(oadErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
2497 QLowEnergyService::OperationError);
2498 oadErrorSpy.clear();
2499
2500 // write invalid descriptor
2501 oadService->readDescriptor(descriptor: invalidDesc);
2502 QTRY_VERIFY_WITH_TIMEOUT(!oadErrorSpy.isEmpty(), 5000);
2503 QCOMPARE(oadErrorSpy.count(), 1);
2504 QCOMPARE(oadErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
2505 QLowEnergyService::OperationError);
2506 oadErrorSpy.clear();
2507
2508 // ********************************************************
2509 // Write to non-writable char
2510
2511 QLowEnergyCharacteristic nonWritableChar = irService->characteristic(uuid: irCharUuid);
2512 QVERIFY(nonWritableChar.isValid());
2513 // not writeable in any form
2514 QVERIFY(!(nonWritableChar.properties()
2515 & (QLowEnergyCharacteristic::Write|QLowEnergyCharacteristic::WriteNoResponse
2516 |QLowEnergyCharacteristic::WriteSigned)));
2517 irService->writeCharacteristic(characteristic: nonWritableChar, newValue: QByteArray("ABCD"));
2518 QTRY_VERIFY_WITH_TIMEOUT(!irErrorSpy.isEmpty(), 5000);
2519 QVERIFY(irWrittenSpy.isEmpty());
2520 QVERIFY(irReadSpy.isEmpty());
2521 QCOMPARE(irErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
2522 QLowEnergyService::CharacteristicWriteError);
2523 irErrorSpy.clear();
2524
2525 // ********************************************************
2526 // Write to non-writable desc
2527 // CharacteristicUserDescription is not writable
2528
2529 QLowEnergyDescriptor nonWritableDesc = nonWritableChar.descriptor(
2530 uuid: QBluetoothUuid::CharacteristicUserDescription);
2531 QVERIFY(nonWritableDesc.isValid());
2532 irService->writeDescriptor(descriptor: nonWritableDesc, newValue: QByteArray("ABCD"));
2533 QTRY_VERIFY_WITH_TIMEOUT(!irErrorSpy.isEmpty(), 5000);
2534 QVERIFY(irWrittenSpy.isEmpty());
2535 QVERIFY(irReadSpy.isEmpty());
2536 QCOMPARE(irErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
2537 QLowEnergyService::DescriptorWriteError);
2538 irErrorSpy.clear();
2539
2540
2541 // ********************************************************
2542 // Read non-readable char
2543
2544 // discover OAD Service
2545 oadService->discoverDetails();
2546 QTRY_VERIFY_WITH_TIMEOUT(
2547 oadService->state() == QLowEnergyService::ServiceDiscovered, 30000);
2548 oadErrorSpy.clear();
2549
2550 // Test reading
2551 QLowEnergyCharacteristic oadChar = oadService->characteristic(uuid: oadCharUuid);
2552 QVERIFY(oadChar.isValid());
2553 oadService->readCharacteristic(characteristic: oadChar);
2554 QTRY_VERIFY_WITH_TIMEOUT(!oadErrorSpy.isEmpty(), 5000);
2555 QCOMPARE(oadErrorSpy.count(), 1);
2556 QVERIFY(oadCharReadSpy.isEmpty());
2557 QCOMPARE(oadErrorSpy[0].at(0).value<QLowEnergyService::ServiceError>(),
2558 QLowEnergyService::CharacteristicReadError);
2559 oadErrorSpy.clear();
2560
2561 delete irService;
2562 delete oadService;
2563 control.disconnectFromDevice();
2564 QTRY_COMPARE(control.state(), QLowEnergyController::UnconnectedState);
2565 QCOMPARE(control.error(), QLowEnergyController::NoError);
2566}
2567
2568/*
2569 Tests write without responses. We utilize the Over-The-Air image update
2570 service of the SensorTag.
2571 */
2572void tst_QLowEnergyController::tst_writeCharacteristicNoResponse()
2573{
2574#if !defined(Q_OS_MACOS) && !QT_CONFIG(winrt_bt)
2575 QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
2576 if (localAdapters.isEmpty())
2577 QSKIP("No local Bluetooth device found. Skipping test.");
2578#endif
2579
2580 if (!remoteDeviceInfo.isValid())
2581 QSKIP("No remote BTLE device found. Skipping test.");
2582 QLowEnergyController control(remoteDeviceInfo);
2583
2584 QCOMPARE(control.error(), QLowEnergyController::NoError);
2585
2586 control.connectToDevice();
2587 {
2588 QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
2589 30000);
2590 }
2591
2592 if (control.state() == QLowEnergyController::ConnectingState
2593 || control.error() != QLowEnergyController::NoError) {
2594 // default BTLE backend forever hangs in ConnectingState
2595 QSKIP("Cannot connect to remote device");
2596 }
2597
2598 QTRY_VERIFY_WITH_TIMEOUT(control.state() == QLowEnergyController::ConnectedState, 20000);
2599 QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
2600 QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
2601 control.discoverServices();
2602 QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 20000);
2603 QCOMPARE(stateSpy.count(), 2);
2604 QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
2605 QLowEnergyController::DiscoveringState);
2606 QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
2607 QLowEnergyController::DiscoveredState);
2608
2609 // The Over-The-Air update service uuid
2610 const QBluetoothUuid testService(QString("f000ffc0-0451-4000-b000-000000000000"));
2611 QList<QBluetoothUuid> uuids = control.services();
2612 QVERIFY(uuids.contains(testService));
2613
2614 QLowEnergyService *service = control.createServiceObject(service: testService, parent: this);
2615 QVERIFY(service);
2616 service->discoverDetails();
2617 QTRY_VERIFY_WITH_TIMEOUT(
2618 service->state() == QLowEnergyService::ServiceDiscovered, 30000);
2619
2620 // 1. Get "Image Identity" and "Image Block" characteristic
2621 const QLowEnergyCharacteristic imageIdentityChar = service->characteristic(
2622 uuid: QBluetoothUuid(QString("f000ffc1-0451-4000-b000-000000000000")));
2623 const QLowEnergyCharacteristic imageBlockChar = service->characteristic(
2624 uuid: QBluetoothUuid(QString("f000ffc2-0451-4000-b000-000000000000")));
2625 QVERIFY(imageIdentityChar.isValid());
2626 QVERIFY(imageIdentityChar.properties() & QLowEnergyCharacteristic::Write);
2627 QVERIFY(imageIdentityChar.properties() & QLowEnergyCharacteristic::WriteNoResponse);
2628 QVERIFY(!(imageIdentityChar.properties() & QLowEnergyCharacteristic::Read)); //not readable
2629 QVERIFY(imageBlockChar.isValid());
2630
2631 // 2. Get "Image Identity" notification descriptor
2632 const QLowEnergyDescriptor identityNotification = imageIdentityChar.descriptor(
2633 uuid: QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
2634 const QLowEnergyDescriptor blockNotification = imageBlockChar.descriptor(
2635 uuid: QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
2636
2637 if (!identityNotification.isValid()
2638 || !blockNotification.isValid()
2639 || !imageIdentityChar.isValid()) {
2640 delete service;
2641 control.disconnectFromDevice();
2642 QTRY_COMPARE(control.state(), QLowEnergyController::UnconnectedState);
2643 QCOMPARE(control.error(), QLowEnergyController::NoError);
2644 QSKIP("Cannot find OAD char/notification");
2645 }
2646
2647 // 3. Enable notifications
2648 QSignalSpy descWrittenSpy(service,
2649 SIGNAL(descriptorWritten(QLowEnergyDescriptor,QByteArray)));
2650 QSignalSpy charChangedSpy(service,
2651 SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)));
2652 QSignalSpy charWrittenSpy(service,
2653 SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)));
2654 QSignalSpy charReadSpy(service,
2655 SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)));
2656 QSignalSpy errorSpy(service,
2657 SIGNAL(error(QLowEnergyService::ServiceError)));
2658
2659 //enable notifications on both characteristics
2660 if (identityNotification.value() != QByteArray::fromHex(hexEncoded: "0100")) {
2661 service->writeDescriptor(descriptor: identityNotification, newValue: QByteArray::fromHex(hexEncoded: "0100"));
2662 QTRY_VERIFY_WITH_TIMEOUT(!descWrittenSpy.isEmpty(), 3000);
2663 QCOMPARE(identityNotification.value(), QByteArray::fromHex("0100"));
2664 QList<QVariant> firstSignalData = descWrittenSpy.first();
2665 QLowEnergyDescriptor signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>();
2666 QByteArray signalValue = firstSignalData[1].toByteArray();
2667 QCOMPARE(signalValue, QByteArray::fromHex("0100"));
2668 QVERIFY(identityNotification == signalDesc);
2669 descWrittenSpy.clear();
2670 }
2671
2672 if (blockNotification.value() != QByteArray::fromHex(hexEncoded: "0100")) {
2673 service->writeDescriptor(descriptor: blockNotification, newValue: QByteArray::fromHex(hexEncoded: "0100"));
2674 QTRY_VERIFY_WITH_TIMEOUT(!descWrittenSpy.isEmpty(), 3000);
2675 QCOMPARE(blockNotification.value(), QByteArray::fromHex("0100"));
2676 QList<QVariant> firstSignalData = descWrittenSpy.first();
2677 QLowEnergyDescriptor signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>();
2678 QByteArray signalValue = firstSignalData[1].toByteArray();
2679 QCOMPARE(signalValue, QByteArray::fromHex("0100"));
2680 QVERIFY(blockNotification == signalDesc);
2681 descWrittenSpy.clear();
2682 }
2683
2684 QList<QVariant> entry;
2685
2686 // Test direct read of non-readable characteristic
2687 QVERIFY(errorSpy.isEmpty());
2688 QVERIFY(charReadSpy.isEmpty());
2689 service->readCharacteristic(characteristic: imageIdentityChar);
2690 QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 10000);
2691 QCOMPARE(errorSpy.count(), 1); // should throw CharacteristicReadError
2692 QVERIFY(charReadSpy.isEmpty());
2693 entry = errorSpy[0];
2694 QCOMPARE(entry[0].value<QLowEnergyService::ServiceError>(),
2695 QLowEnergyService::CharacteristicReadError);
2696
2697 // 4. Trigger image identity announcement (using traditional write)
2698 bool foundOneImage = false;
2699
2700 // Image A
2701 // Write triggers a notification and write confirmation
2702 service->writeCharacteristic(characteristic: imageIdentityChar, newValue: QByteArray::fromHex(hexEncoded: "0"));
2703 QTest::qWait(ms: 1000);
2704 QTRY_COMPARE_WITH_TIMEOUT(charChangedSpy.count(), 1, 5000);
2705 QTRY_COMPARE_WITH_TIMEOUT(charWrittenSpy.count(), 1, 5000);
2706
2707 // This is very SensorTag specific logic.
2708 // If the image block is empty the current firmware
2709 // does not even send a notification for imageIdentityChar
2710 // but for imageBlockChar
2711
2712 entry = charChangedSpy[0];
2713 QLowEnergyCharacteristic first = entry[0].value<QLowEnergyCharacteristic>();
2714 QByteArray val1 = entry[1].toByteArray();
2715 if (val1.size() == 8) {
2716 QCOMPARE(imageIdentityChar, first);
2717 foundOneImage = true;
2718 } else {
2719 // we received a notification for imageBlockChar
2720 QCOMPARE(imageBlockChar, first);
2721 qWarning() << "Invalid image A ident info";
2722 }
2723
2724 entry = charWrittenSpy[0];
2725 QLowEnergyCharacteristic second = entry[0].value<QLowEnergyCharacteristic>();
2726 QByteArray val2 = entry[1].toByteArray();
2727 QCOMPARE(imageIdentityChar, second);
2728 QVERIFY(val2 == QByteArray::fromHex("0") || val2 == val1);
2729
2730 // notifications on non-readable characteristics do not update cache
2731 QVERIFY(imageIdentityChar.value().isEmpty());
2732 QVERIFY(imageBlockChar.value().isEmpty());
2733
2734 charChangedSpy.clear();
2735 charWrittenSpy.clear();
2736
2737 // Image B
2738 service->writeCharacteristic(characteristic: imageIdentityChar, newValue: QByteArray::fromHex(hexEncoded: "1"));
2739 QTest::qWait(ms: 1000);
2740 QTRY_COMPARE_WITH_TIMEOUT(charChangedSpy.count(), 1, 5000);
2741 QTRY_COMPARE_WITH_TIMEOUT(charWrittenSpy.count(), 1, 5000);;
2742
2743 entry = charChangedSpy[0];
2744 first = entry[0].value<QLowEnergyCharacteristic>();
2745 val1 = entry[1].toByteArray();
2746 if (val1.size() == 8) {
2747 QCOMPARE(imageIdentityChar, first);
2748 foundOneImage = true;
2749 } else {
2750 // we received a notification for imageBlockChar without explicitly
2751 // enabling them. This is caused by the device's default settings.
2752 QCOMPARE(imageBlockChar, first);
2753 qWarning() << "Invalid image B ident info";
2754 }
2755
2756 entry = charWrittenSpy[0];
2757 second = entry[0].value<QLowEnergyCharacteristic>();
2758 val2 = entry[1].toByteArray();
2759 QCOMPARE(imageIdentityChar, second);
2760
2761 // notifications on non-readable characteristics do not update cache
2762 QVERIFY(imageIdentityChar.value().isEmpty());
2763 QVERIFY(imageBlockChar.value().isEmpty());
2764
2765 /* Bluez resends the last confirmed write value, other platforms
2766 * send the value received by the change notification value.
2767 */
2768 qDebug() << "Image B(1):" << val1.toHex() << val2.toHex();
2769 QVERIFY(val2 == QByteArray::fromHex("1") || val2 == val1);
2770
2771 QVERIFY2(foundOneImage, "The SensorTag doesn't have a valid image? (1)");
2772
2773 // 5. Trigger image identity announcement (without response)
2774 charChangedSpy.clear();
2775 charWrittenSpy.clear();
2776 foundOneImage = false;
2777
2778 // Image A
2779 service->writeCharacteristic(characteristic: imageIdentityChar,
2780 newValue: QByteArray::fromHex(hexEncoded: "0"),
2781 mode: QLowEnergyService::WriteWithoutResponse);
2782
2783 // we only expect one signal (the notification but not the write confirmation)
2784 // Wait at least a second for a potential second signals
2785 QTest::qWait(ms: 1000);
2786 QTRY_COMPARE_WITH_TIMEOUT(charChangedSpy.count(), 1, 10000);
2787 QTRY_COMPARE_WITH_TIMEOUT(charWrittenSpy.count(), 0, 10000);
2788
2789 entry = charChangedSpy[0];
2790 first = entry[0].value<QLowEnergyCharacteristic>();
2791 val1 = entry[1].toByteArray();
2792
2793#ifdef Q_OS_ANDROID
2794 QEXPECT_FAIL("", "Android sends write confirmation when using WriteWithoutResponse",
2795 Continue);
2796#endif
2797 QVERIFY(charWrittenSpy.isEmpty());
2798 if (val1.size() == 8) {
2799 QCOMPARE(first, imageIdentityChar);
2800 foundOneImage = true;
2801 } else {
2802 // we received a notification for imageBlockChar without explicitly
2803 // enabling them. This is caused by the device's default settings.
2804 QCOMPARE(imageBlockChar, first);
2805 qWarning() << "Image A not set?";
2806 }
2807
2808 // notifications on non-readable characteristics do not update cache
2809 QVERIFY(imageIdentityChar.value().isEmpty());
2810 QVERIFY(imageBlockChar.value().isEmpty());
2811
2812 charChangedSpy.clear();
2813
2814 // Image B
2815 service->writeCharacteristic(characteristic: imageIdentityChar,
2816 newValue: QByteArray::fromHex(hexEncoded: "1"),
2817 mode: QLowEnergyService::WriteWithoutResponse);
2818
2819 // we only expect one signal (the notification but not the write confirmation)
2820 // Wait at least a second for a potential second signals
2821 QTest::qWait(ms: 1000);
2822 QTRY_COMPARE_WITH_TIMEOUT(charWrittenSpy.count(), 0, 10000);
2823 QTRY_COMPARE_WITH_TIMEOUT(charChangedSpy.count(), 1, 10000);
2824
2825 entry = charChangedSpy[0];
2826 first = entry[0].value<QLowEnergyCharacteristic>();
2827 val1 = entry[1].toByteArray();
2828
2829#ifdef Q_OS_ANDROID
2830 QEXPECT_FAIL("", "Android sends write confirmation when using WriteWithoutResponse",
2831 Continue);
2832#endif
2833 QVERIFY(charWrittenSpy.isEmpty());
2834 if (val1.size() == 8) {
2835 QCOMPARE(first, imageIdentityChar);
2836 foundOneImage = true;
2837 } else {
2838 // we received a notification for imageBlockChar without explicitly
2839 // enabling them. This is caused by the device's default settings.
2840 QCOMPARE(imageBlockChar, first);
2841 qWarning() << "Image B not set?";
2842 }
2843
2844 // notifications on non-readable characteristics do not update cache
2845 QVERIFY(imageIdentityChar.value().isEmpty());
2846 QVERIFY(imageBlockChar.value().isEmpty());
2847
2848
2849 QVERIFY2(foundOneImage, "The SensorTag doesn't have a valid image? (2)");
2850
2851 delete service;
2852 control.disconnectFromDevice();
2853 QTRY_COMPARE(control.state(), QLowEnergyController::UnconnectedState);
2854 QCOMPARE(control.error(), QLowEnergyController::NoError);
2855}
2856
2857QTEST_MAIN(tst_QLowEnergyController)
2858
2859#include "tst_qlowenergycontroller.moc"
2860

source code of qtconnectivity/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp