| 1 | /*************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 BlackBerry Limited all rights reserved | 
| 4 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 5 | ** Contact: https://www.qt.io/licensing/ | 
| 6 | ** | 
| 7 | ** This file is part of the QtBluetooth module of the Qt Toolkit. | 
| 8 | ** | 
| 9 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | 
| 10 | ** Commercial License Usage | 
| 11 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 12 | ** accordance with the commercial license agreement provided with the | 
| 13 | ** Software or, alternatively, in accordance with the terms contained in | 
| 14 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 15 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 16 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 17 | ** | 
| 18 | ** GNU General Public License Usage | 
| 19 | ** Alternatively, this file may be used under the terms of the GNU | 
| 20 | ** General Public License version 3 as published by the Free Software | 
| 21 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
| 22 | ** included in the packaging of this file. Please review the following | 
| 23 | ** information to ensure the GNU General Public License requirements will | 
| 24 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | 
| 25 | ** | 
| 26 | ** $QT_END_LICENSE$ | 
| 27 | ** | 
| 28 | ****************************************************************************/ | 
| 29 |  | 
| 30 | #include <QtTest/QtTest> | 
| 31 | #include <QUuid> | 
| 32 |  | 
| 33 | #include <QDebug> | 
| 34 |  | 
| 35 | #include <QBluetoothDeviceDiscoveryAgent> | 
| 36 | #include <QLowEnergyCharacteristic> | 
| 37 | #include <QLowEnergyController> | 
| 38 | #include <QBluetoothLocalDevice> | 
| 39 |  | 
| 40 | QT_USE_NAMESPACE | 
| 41 |  | 
| 42 | // This define must be set if the platform provides access to GATT handles | 
| 43 | // otherwise it must not be defined. As of now the two supported platforms | 
| 44 | // (Android and Bluez/Linux) provide access or some notion of it. | 
| 45 | #define HANDLES_PROVIDED_BY_PLATFORM | 
| 46 |  | 
| 47 | #ifdef HANDLES_PROVIDED_BY_PLATFORM | 
| 48 | #define HANDLE_VERIFY(stmt) QVERIFY(stmt) | 
| 49 | #else | 
| 50 | #define HANDLE_VERIFY(stmt) | 
| 51 | #endif | 
| 52 |  | 
| 53 | class tst_QLowEnergyCharacteristic : public QObject | 
| 54 | { | 
| 55 |     Q_OBJECT | 
| 56 |  | 
| 57 | public: | 
| 58 |     tst_QLowEnergyCharacteristic(); | 
| 59 |     ~tst_QLowEnergyCharacteristic(); | 
| 60 |  | 
| 61 | protected slots: | 
| 62 |     void deviceDiscovered(const QBluetoothDeviceInfo &info); | 
| 63 |  | 
| 64 | private slots: | 
| 65 |     void initTestCase(); | 
| 66 |     void cleanupTestCase(); | 
| 67 |     void tst_constructionDefault(); | 
| 68 |     void tst_assignCompare(); | 
| 69 |  | 
| 70 | private: | 
| 71 |     QList<QBluetoothDeviceInfo> remoteLeDevices; | 
| 72 |     QLowEnergyController *globalControl; | 
| 73 |     QLowEnergyService *globalService; | 
| 74 | }; | 
| 75 |  | 
| 76 | tst_QLowEnergyCharacteristic::tst_QLowEnergyCharacteristic() : | 
| 77 |     globalControl(0), globalService(0) | 
| 78 | { | 
| 79 |     QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true" )); | 
| 80 |     qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>(); | 
| 81 | } | 
| 82 |  | 
| 83 | tst_QLowEnergyCharacteristic::~tst_QLowEnergyCharacteristic() | 
| 84 | { | 
| 85 | } | 
| 86 |  | 
| 87 | void tst_QLowEnergyCharacteristic::initTestCase() | 
| 88 | { | 
| 89 |     if (QBluetoothLocalDevice::allDevices().isEmpty()) { | 
| 90 |         qWarning(msg: "No remote device discovered." ); | 
| 91 |         return; | 
| 92 |     } | 
| 93 |  | 
| 94 |     // start Bluetooth if not started | 
| 95 |     QBluetoothLocalDevice device; | 
| 96 |     device.powerOn(); | 
| 97 |  | 
| 98 |     // find an arbitrary low energy device in vincinity | 
| 99 |     // find an arbitrary service with characteristic | 
| 100 |     QBluetoothDeviceDiscoveryAgent *devAgent = new QBluetoothDeviceDiscoveryAgent(this); | 
| 101 |     connect(sender: devAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)), | 
| 102 |             receiver: this, SLOT(deviceDiscovered(QBluetoothDeviceInfo))); | 
| 103 |  | 
| 104 |     QSignalSpy errorSpy(devAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error))); | 
| 105 |     QVERIFY(errorSpy.isValid()); | 
| 106 |     QVERIFY(errorSpy.isEmpty()); | 
| 107 |  | 
| 108 |     QSignalSpy spy(devAgent, SIGNAL(finished())); | 
| 109 |     QVERIFY(spy.isValid()); | 
| 110 |     QVERIFY(spy.isEmpty()); | 
| 111 |  | 
| 112 |     devAgent->start(); | 
| 113 |     QTRY_VERIFY_WITH_TIMEOUT(spy.count() > 0, 50000); | 
| 114 |  | 
| 115 |     // find first service with descriptor | 
| 116 |     QLowEnergyController *controller = 0; | 
| 117 |     for (const QBluetoothDeviceInfo &remoteDevice : qAsConst(t&: remoteLeDevices)) { | 
| 118 |         controller = QLowEnergyController::createCentral(remoteDevice, parent: this); | 
| 119 |         qDebug() << "Connecting to"  << remoteDevice.name() | 
| 120 |                  << remoteDevice.address() << remoteDevice.deviceUuid(); | 
| 121 |         controller->connectToDevice(); | 
| 122 |         QTRY_IMPL(controller->state() != QLowEnergyController::ConnectingState, | 
| 123 |                   20000); | 
| 124 |         if (controller->state() != QLowEnergyController::ConnectedState) { | 
| 125 |             // any error and we skip | 
| 126 |             delete controller; | 
| 127 |             qDebug() << "Skipping device" ; | 
| 128 |             continue; | 
| 129 |         } | 
| 130 |  | 
| 131 |         QSignalSpy discoveryFinishedSpy(controller, SIGNAL(discoveryFinished())); | 
| 132 |         QSignalSpy stateSpy(controller, SIGNAL(stateChanged(QLowEnergyController::ControllerState))); | 
| 133 |         controller->discoverServices(); | 
| 134 |         QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 10000); | 
| 135 |         QCOMPARE(stateSpy.count(), 2); | 
| 136 |         QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(), | 
| 137 |                  QLowEnergyController::DiscoveringState); | 
| 138 |         QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(), | 
| 139 |                  QLowEnergyController::DiscoveredState); | 
| 140 |  | 
| 141 |         const QList<QBluetoothUuid> leServiceUuids = controller->services(); | 
| 142 |         for (const QBluetoothUuid &leServiceUuid : leServiceUuids) { | 
| 143 |             QLowEnergyService *leService = controller->createServiceObject(service: leServiceUuid, parent: this); | 
| 144 |             if (!leService) | 
| 145 |                 continue; | 
| 146 |  | 
| 147 |             leService->discoverDetails(); | 
| 148 |             QTRY_VERIFY_WITH_TIMEOUT( | 
| 149 |                         leService->state() == QLowEnergyService::ServiceDiscovered, 10000); | 
| 150 |  | 
| 151 |             const QList<QLowEnergyCharacteristic> chars = leService->characteristics(); | 
| 152 |             for (const QLowEnergyCharacteristic &ch : chars) { | 
| 153 |                 if (!ch.descriptors().isEmpty()) { | 
| 154 |                     globalService = leService; | 
| 155 |                     globalControl = controller; | 
| 156 |                     qWarning() << "Found service with descriptor"  << remoteDevice.address() | 
| 157 |                                << globalService->serviceName() << globalService->serviceUuid(); | 
| 158 |                     break; | 
| 159 |                 } | 
| 160 |             } | 
| 161 |  | 
| 162 |             if (globalControl) | 
| 163 |                 break; | 
| 164 |             else | 
| 165 |                 delete leService; | 
| 166 |         } | 
| 167 |  | 
| 168 |         if (globalControl) | 
| 169 |             break; | 
| 170 |  | 
| 171 |         delete controller; | 
| 172 |     } | 
| 173 |  | 
| 174 |     if (!globalControl) { | 
| 175 |         qWarning() << "Test limited due to missing remote QLowEnergyDescriptor."  | 
| 176 |                    << "Please ensure the Bluetooth Low Energy device is advertising its services." ; | 
| 177 |     } | 
| 178 | } | 
| 179 |  | 
| 180 | void tst_QLowEnergyCharacteristic::cleanupTestCase() | 
| 181 | { | 
| 182 |     if (globalControl) | 
| 183 |         globalControl->disconnectFromDevice(); | 
| 184 | } | 
| 185 |  | 
| 186 | void tst_QLowEnergyCharacteristic::deviceDiscovered(const QBluetoothDeviceInfo &info) | 
| 187 | { | 
| 188 |     if (info.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) | 
| 189 |         remoteLeDevices.append(t: info); | 
| 190 | } | 
| 191 |  | 
| 192 | void tst_QLowEnergyCharacteristic::tst_constructionDefault() | 
| 193 | { | 
| 194 |     QLowEnergyCharacteristic characteristic; | 
| 195 |     QVERIFY(!characteristic.isValid()); | 
| 196 |     QCOMPARE(characteristic.value(), QByteArray()); | 
| 197 |     QVERIFY(characteristic.uuid().isNull()); | 
| 198 |     QVERIFY(characteristic.handle() == 0); | 
| 199 |     QCOMPARE(characteristic.name(), QString()); | 
| 200 |     QCOMPARE(characteristic.descriptors().count(), 0); | 
| 201 |     QCOMPARE(characteristic.descriptor(QBluetoothUuid()), | 
| 202 |              QLowEnergyDescriptor()); | 
| 203 |     QCOMPARE(characteristic.descriptor(QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)), | 
| 204 |              QLowEnergyDescriptor()); | 
| 205 |     QCOMPARE(characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), | 
| 206 |              QLowEnergyDescriptor()); | 
| 207 |     QCOMPARE(characteristic.properties(), QLowEnergyCharacteristic::Unknown); | 
| 208 |  | 
| 209 |     QLowEnergyCharacteristic copyConstructed(characteristic); | 
| 210 |     QVERIFY(!copyConstructed.isValid()); | 
| 211 |     QCOMPARE(copyConstructed.value(), QByteArray()); | 
| 212 |     QVERIFY(copyConstructed.uuid().isNull()); | 
| 213 |     QVERIFY(copyConstructed.handle() == 0); | 
| 214 |     QCOMPARE(copyConstructed.name(), QString()); | 
| 215 |     QCOMPARE(copyConstructed.descriptors().count(), 0); | 
| 216 |     QCOMPARE(copyConstructed.properties(), QLowEnergyCharacteristic::Unknown); | 
| 217 |  | 
| 218 |     QVERIFY(copyConstructed == characteristic); | 
| 219 |     QVERIFY(characteristic == copyConstructed); | 
| 220 |     QVERIFY(!(copyConstructed != characteristic)); | 
| 221 |     QVERIFY(!(characteristic != copyConstructed)); | 
| 222 |  | 
| 223 |     QLowEnergyCharacteristic assigned; | 
| 224 |  | 
| 225 |     QVERIFY(assigned == characteristic); | 
| 226 |     QVERIFY(characteristic == assigned); | 
| 227 |     QVERIFY(!(assigned != characteristic)); | 
| 228 |     QVERIFY(!(characteristic != assigned)); | 
| 229 |  | 
| 230 |     assigned = characteristic; | 
| 231 |     QVERIFY(!assigned.isValid()); | 
| 232 |     QCOMPARE(assigned.value(), QByteArray()); | 
| 233 |     QVERIFY(assigned.uuid().isNull()); | 
| 234 |     QVERIFY(assigned.handle() == 0); | 
| 235 |     QCOMPARE(assigned.name(), QString()); | 
| 236 |     QCOMPARE(assigned.descriptors().count(), 0); | 
| 237 |     QCOMPARE(assigned.properties(), QLowEnergyCharacteristic::Unknown); | 
| 238 |  | 
| 239 |     QVERIFY(assigned == characteristic); | 
| 240 |     QVERIFY(characteristic == assigned); | 
| 241 |     QVERIFY(!(assigned != characteristic)); | 
| 242 |     QVERIFY(!(characteristic != assigned)); | 
| 243 | } | 
| 244 |  | 
| 245 | void tst_QLowEnergyCharacteristic::tst_assignCompare() | 
| 246 | { | 
| 247 |     if (!globalService) | 
| 248 |         QSKIP("No characteristic found." ); | 
| 249 |  | 
| 250 |     QLowEnergyCharacteristic target; | 
| 251 |     QVERIFY(!target.isValid()); | 
| 252 |     QCOMPARE(target.value(), QByteArray()); | 
| 253 |     QVERIFY(target.uuid().isNull()); | 
| 254 |     QVERIFY(target.handle() == 0); | 
| 255 |     QCOMPARE(target.name(), QString()); | 
| 256 |     QCOMPARE(target.descriptors().count(), 0); | 
| 257 |     QCOMPARE(target.properties(), QLowEnergyCharacteristic::Unknown); | 
| 258 |  | 
| 259 |     int indexWithDescriptor = -1; | 
| 260 |     const QList<QLowEnergyCharacteristic> chars = globalService->characteristics(); | 
| 261 |     QVERIFY(!chars.isEmpty()); | 
| 262 |     for (int i = 0; i < chars.count(); i++) { | 
| 263 |         const QLowEnergyCharacteristic specific = | 
| 264 |                 globalService->characteristic(uuid: chars[i].uuid()); | 
| 265 |         QVERIFY(specific.isValid()); | 
| 266 |         QCOMPARE(specific, chars[i]); | 
| 267 |         if (chars[i].descriptors().count() > 0) { | 
| 268 |             indexWithDescriptor = i; | 
| 269 |             break; | 
| 270 |         } | 
| 271 |     } | 
| 272 |  | 
| 273 |     if (chars.isEmpty()) | 
| 274 |         QSKIP("No suitable characteristic found despite prior indication." ); | 
| 275 |  | 
| 276 |     bool noDescriptors = (indexWithDescriptor == -1); | 
| 277 |     if (noDescriptors) | 
| 278 |         indexWithDescriptor = 0; // just choose one | 
| 279 |  | 
| 280 |     // test assignment operator | 
| 281 |     target = chars[indexWithDescriptor]; | 
| 282 |     QVERIFY(target.isValid()); | 
| 283 |     QVERIFY(!target.name().isEmpty()); | 
| 284 |     HANDLE_VERIFY(target.handle() > 0); | 
| 285 |     QVERIFY(!target.uuid().isNull()); | 
| 286 |     QVERIFY(target.properties() != QLowEnergyCharacteristic::Unknown); | 
| 287 |     if (target.properties() & QLowEnergyCharacteristic::Read) | 
| 288 |         QVERIFY(!target.value().isEmpty()); | 
| 289 |     if (!noDescriptors) | 
| 290 |         QVERIFY(target.descriptors().count() > 0); | 
| 291 |  | 
| 292 |     QVERIFY(target == chars[indexWithDescriptor]); | 
| 293 |     QVERIFY(chars[indexWithDescriptor] == target); | 
| 294 |     QVERIFY(!(target != chars[indexWithDescriptor])); | 
| 295 |     QVERIFY(!(chars[indexWithDescriptor] != target)); | 
| 296 |  | 
| 297 |     QCOMPARE(target.isValid(), chars[indexWithDescriptor].isValid()); | 
| 298 |     QCOMPARE(target.name(), chars[indexWithDescriptor].name()); | 
| 299 |     QCOMPARE(target.handle(), chars[indexWithDescriptor].handle()); | 
| 300 |     QCOMPARE(target.uuid(), chars[indexWithDescriptor].uuid()); | 
| 301 |     QCOMPARE(target.value(), chars[indexWithDescriptor].value()); | 
| 302 |     QCOMPARE(target.properties(), chars[indexWithDescriptor].properties()); | 
| 303 |     QCOMPARE(target.descriptors().count(), | 
| 304 |              chars[indexWithDescriptor].descriptors().count()); | 
| 305 |     for (int i = 0; i < target.descriptors().count(); i++) { | 
| 306 |         const QLowEnergyDescriptor ref = chars[indexWithDescriptor].descriptors()[i]; | 
| 307 |         QCOMPARE(target.descriptors()[i].name(), ref.name()); | 
| 308 |         QCOMPARE(target.descriptors()[i].isValid(), ref.isValid()); | 
| 309 |         QCOMPARE(target.descriptors()[i].type(), ref.type()); | 
| 310 |         QCOMPARE(target.descriptors()[i].handle(), ref.handle()); | 
| 311 |         QCOMPARE(target.descriptors()[i].uuid(), ref.uuid()); | 
| 312 |         QCOMPARE(target.descriptors()[i].value(), ref.value()); | 
| 313 |  | 
| 314 |         const QLowEnergyDescriptor ref2 = chars[indexWithDescriptor].descriptor(uuid: ref.uuid()); | 
| 315 |         QCOMPARE(ref, ref2); | 
| 316 |     } | 
| 317 |  | 
| 318 |     // test copy constructor | 
| 319 |     QLowEnergyCharacteristic copyConstructed(target); | 
| 320 |     QCOMPARE(copyConstructed.isValid(), chars[indexWithDescriptor].isValid()); | 
| 321 |     QCOMPARE(copyConstructed.name(), chars[indexWithDescriptor].name()); | 
| 322 |     QCOMPARE(copyConstructed.handle(), chars[indexWithDescriptor].handle()); | 
| 323 |     QCOMPARE(copyConstructed.uuid(), chars[indexWithDescriptor].uuid()); | 
| 324 |     QCOMPARE(copyConstructed.value(), chars[indexWithDescriptor].value()); | 
| 325 |     QCOMPARE(copyConstructed.properties(), chars[indexWithDescriptor].properties()); | 
| 326 |     QCOMPARE(copyConstructed.descriptors().count(), | 
| 327 |              chars[indexWithDescriptor].descriptors().count()); | 
| 328 |  | 
| 329 |     QVERIFY(copyConstructed == target); | 
| 330 |     QVERIFY(target == copyConstructed); | 
| 331 |     QVERIFY(!(copyConstructed != target)); | 
| 332 |     QVERIFY(!(target != copyConstructed)); | 
| 333 |  | 
| 334 |     // test invalidation | 
| 335 |     QLowEnergyCharacteristic invalid; | 
| 336 |     target = invalid; | 
| 337 |     QVERIFY(!target.isValid()); | 
| 338 |     QCOMPARE(target.value(), QByteArray()); | 
| 339 |     QVERIFY(target.uuid().isNull()); | 
| 340 |     QVERIFY(target.handle() == 0); | 
| 341 |     QCOMPARE(target.name(), QString()); | 
| 342 |     QCOMPARE(target.descriptors().count(), 0); | 
| 343 |     QCOMPARE(target.properties(), QLowEnergyCharacteristic::Unknown); | 
| 344 |  | 
| 345 |     QVERIFY(invalid == target); | 
| 346 |     QVERIFY(target == invalid); | 
| 347 |     QVERIFY(!(invalid != target)); | 
| 348 |     QVERIFY(!(target != invalid)); | 
| 349 |  | 
| 350 |     QVERIFY(!(chars[indexWithDescriptor] == target)); | 
| 351 |     QVERIFY(!(target == chars[indexWithDescriptor])); | 
| 352 |     QVERIFY(chars[indexWithDescriptor] != target); | 
| 353 |     QVERIFY(target != chars[indexWithDescriptor]); | 
| 354 |  | 
| 355 |     if (chars.count() >= 2) { | 
| 356 |         // at least two characteristics | 
| 357 |         QVERIFY(!(chars[0] == chars[1])); | 
| 358 |         QVERIFY(!(chars[1] == chars[0])); | 
| 359 |         QVERIFY(chars[0] != chars[1]); | 
| 360 |         QVERIFY(chars[1] != chars[0]); | 
| 361 |     } | 
| 362 | } | 
| 363 |  | 
| 364 | QTEST_MAIN(tst_QLowEnergyCharacteristic) | 
| 365 |  | 
| 366 | #include "tst_qlowenergycharacteristic.moc" | 
| 367 |  |