| 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 | |