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
40QT_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
53class tst_QLowEnergyCharacteristic : public QObject
54{
55 Q_OBJECT
56
57public:
58 tst_QLowEnergyCharacteristic();
59 ~tst_QLowEnergyCharacteristic();
60
61protected slots:
62 void deviceDiscovered(const QBluetoothDeviceInfo &info);
63
64private slots:
65 void initTestCase();
66 void cleanupTestCase();
67 void tst_constructionDefault();
68 void tst_assignCompare();
69
70private:
71 QList<QBluetoothDeviceInfo> remoteLeDevices;
72 QLowEnergyController *globalControl;
73 QLowEnergyService *globalService;
74};
75
76tst_QLowEnergyCharacteristic::tst_QLowEnergyCharacteristic() :
77 globalControl(0), globalService(0)
78{
79 QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
80 qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>();
81}
82
83tst_QLowEnergyCharacteristic::~tst_QLowEnergyCharacteristic()
84{
85}
86
87void 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
180void tst_QLowEnergyCharacteristic::cleanupTestCase()
181{
182 if (globalControl)
183 globalControl->disconnectFromDevice();
184}
185
186void tst_QLowEnergyCharacteristic::deviceDiscovered(const QBluetoothDeviceInfo &info)
187{
188 if (info.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration)
189 remoteLeDevices.append(t: info);
190}
191
192void 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
245void 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
364QTEST_MAIN(tst_QLowEnergyCharacteristic)
365
366#include "tst_qlowenergycharacteristic.moc"
367

source code of qtconnectivity/tests/auto/qlowenergycharacteristic/tst_qlowenergycharacteristic.cpp