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 | |
54 | QT_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 | |
72 | class tst_QLowEnergyController : public QObject |
73 | { |
74 | Q_OBJECT |
75 | |
76 | public: |
77 | tst_QLowEnergyController(); |
78 | ~tst_QLowEnergyController(); |
79 | |
80 | private 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(); |
93 | private: |
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 | |
104 | tst_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 | |
126 | tst_QLowEnergyController::~tst_QLowEnergyController() |
127 | { |
128 | |
129 | } |
130 | |
131 | void 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 | */ |
205 | void 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 | |
216 | void tst_QLowEnergyController::cleanupTestCase() |
217 | { |
218 | |
219 | } |
220 | |
221 | void 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 | |
263 | void 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 | |
425 | void 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 | |
639 | void 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 | * */ |
1661 | bool 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 | |
1672 | void 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 | |
1722 | void 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 | |
1899 | void 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 | */ |
2190 | void 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 | */ |
2348 | void 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 | */ |
2572 | void 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 | |
2857 | QTEST_MAIN(tst_QLowEnergyController) |
2858 | |
2859 | #include "tst_qlowenergycontroller.moc" |
2860 | |