1/***************************************************************************
2**
3** Copyright (C) 2013 BlackBerry Limited. All rights reserved.
4** Copyright (C) 2017 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:BSD$
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** BSD License Usage
19** Alternatively, you may use this file under the terms of the BSD license
20** as follows:
21**
22** "Redistribution and use in source and binary forms, with or without
23** modification, are permitted provided that the following conditions are
24** met:
25** * Redistributions of source code must retain the above copyright
26** notice, this list of conditions and the following disclaimer.
27** * Redistributions in binary form must reproduce the above copyright
28** notice, this list of conditions and the following disclaimer in
29** the documentation and/or other materials provided with the
30** distribution.
31** * Neither the name of The Qt Company Ltd nor the names of its
32** contributors may be used to endorse or promote products derived
33** from this software without specific prior written permission.
34**
35**
36** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
37** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
38** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
39** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
40** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
43** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
45** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
46** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
47**
48** $QT_END_LICENSE$
49**
50****************************************************************************/
51
52#include "device.h"
53
54#include <qbluetoothaddress.h>
55#include <qbluetoothdevicediscoveryagent.h>
56#include <qbluetoothlocaldevice.h>
57#include <qbluetoothdeviceinfo.h>
58#include <qbluetoothservicediscoveryagent.h>
59#include <QDebug>
60#include <QList>
61#include <QMetaEnum>
62#include <QTimer>
63
64Device::Device()
65{
66 //! [les-devicediscovery-1]
67 discoveryAgent = new QBluetoothDeviceDiscoveryAgent();
68 discoveryAgent->setLowEnergyDiscoveryTimeout(5000);
69 connect(sender: discoveryAgent, signal: &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
70 receiver: this, slot: &Device::addDevice);
71 connect(sender: discoveryAgent, signal: QOverload<QBluetoothDeviceDiscoveryAgent::Error>::of(ptr: &QBluetoothDeviceDiscoveryAgent::error),
72 receiver: this, slot: &Device::deviceScanError);
73 connect(sender: discoveryAgent, signal: &QBluetoothDeviceDiscoveryAgent::finished, receiver: this, slot: &Device::deviceScanFinished);
74 //! [les-devicediscovery-1]
75
76 setUpdate("Search");
77}
78
79Device::~Device()
80{
81 delete discoveryAgent;
82 delete controller;
83 qDeleteAll(c: devices);
84 qDeleteAll(c: m_services);
85 qDeleteAll(c: m_characteristics);
86 devices.clear();
87 m_services.clear();
88 m_characteristics.clear();
89}
90
91void Device::startDeviceDiscovery()
92{
93 qDeleteAll(c: devices);
94 devices.clear();
95 emit devicesUpdated();
96
97 setUpdate("Scanning for devices ...");
98 //! [les-devicediscovery-2]
99 discoveryAgent->start(method: QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
100 //! [les-devicediscovery-2]
101
102 if (discoveryAgent->isActive()) {
103 m_deviceScanState = true;
104 Q_EMIT stateChanged();
105 }
106}
107
108//! [les-devicediscovery-3]
109void Device::addDevice(const QBluetoothDeviceInfo &info)
110{
111 if (info.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration)
112 setUpdate("Last device added: " + info.name());
113}
114//! [les-devicediscovery-3]
115
116void Device::deviceScanFinished()
117{
118 const QList<QBluetoothDeviceInfo> foundDevices = discoveryAgent->discoveredDevices();
119 for (auto nextDevice : foundDevices)
120 if (nextDevice.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration)
121 devices.append(t: new DeviceInfo(nextDevice));
122
123 emit devicesUpdated();
124 m_deviceScanState = false;
125 emit stateChanged();
126 if (devices.isEmpty())
127 setUpdate("No Low Energy devices found...");
128 else
129 setUpdate("Done! Scan Again!");
130}
131
132QVariant Device::getDevices()
133{
134 return QVariant::fromValue(value: devices);
135}
136
137QVariant Device::getServices()
138{
139 return QVariant::fromValue(value: m_services);
140}
141
142QVariant Device::getCharacteristics()
143{
144 return QVariant::fromValue(value: m_characteristics);
145}
146
147QString Device::getUpdate()
148{
149 return m_message;
150}
151
152void Device::scanServices(const QString &address)
153{
154 // We need the current device for service discovery.
155
156 for (auto d: qAsConst(t&: devices)) {
157 if (auto device = qobject_cast<DeviceInfo *>(object: d)) {
158 if (device->getAddress() == address ) {
159 currentDevice.setDevice(device->getDevice());
160 break;
161 }
162 }
163 }
164
165 if (!currentDevice.getDevice().isValid()) {
166 qWarning() << "Not a valid device";
167 return;
168 }
169
170 qDeleteAll(c: m_characteristics);
171 m_characteristics.clear();
172 emit characteristicsUpdated();
173 qDeleteAll(c: m_services);
174 m_services.clear();
175 emit servicesUpdated();
176
177 setUpdate("Back\n(Connecting to device...)");
178
179 if (controller && m_previousAddress != currentDevice.getAddress()) {
180 controller->disconnectFromDevice();
181 delete controller;
182 controller = nullptr;
183 }
184
185 //! [les-controller-1]
186 if (!controller) {
187 // Connecting signals and slots for connecting to LE services.
188 controller = QLowEnergyController::createCentral(remoteDevice: currentDevice.getDevice());
189 connect(sender: controller, signal: &QLowEnergyController::connected,
190 receiver: this, slot: &Device::deviceConnected);
191 connect(sender: controller, signal: QOverload<QLowEnergyController::Error>::of(ptr: &QLowEnergyController::error),
192 receiver: this, slot: &Device::errorReceived);
193 connect(sender: controller, signal: &QLowEnergyController::disconnected,
194 receiver: this, slot: &Device::deviceDisconnected);
195 connect(sender: controller, signal: &QLowEnergyController::serviceDiscovered,
196 receiver: this, slot: &Device::addLowEnergyService);
197 connect(sender: controller, signal: &QLowEnergyController::discoveryFinished,
198 receiver: this, slot: &Device::serviceScanDone);
199 }
200
201 if (isRandomAddress())
202 controller->setRemoteAddressType(QLowEnergyController::RandomAddress);
203 else
204 controller->setRemoteAddressType(QLowEnergyController::PublicAddress);
205 controller->connectToDevice();
206 //! [les-controller-1]
207
208 m_previousAddress = currentDevice.getAddress();
209}
210
211void Device::addLowEnergyService(const QBluetoothUuid &serviceUuid)
212{
213 //! [les-service-1]
214 QLowEnergyService *service = controller->createServiceObject(service: serviceUuid);
215 if (!service) {
216 qWarning() << "Cannot create service for uuid";
217 return;
218 }
219 //! [les-service-1]
220 auto serv = new ServiceInfo(service);
221 m_services.append(t: serv);
222
223 emit servicesUpdated();
224}
225//! [les-service-1]
226
227void Device::serviceScanDone()
228{
229 setUpdate("Back\n(Service scan done!)");
230 // force UI in case we didn't find anything
231 if (m_services.isEmpty())
232 emit servicesUpdated();
233}
234
235void Device::connectToService(const QString &uuid)
236{
237 QLowEnergyService *service = nullptr;
238 for (auto s: qAsConst(t&: m_services)) {
239 auto serviceInfo = qobject_cast<ServiceInfo *>(object: s);
240 if (!serviceInfo)
241 continue;
242
243 if (serviceInfo->getUuid() == uuid) {
244 service = serviceInfo->service();
245 break;
246 }
247 }
248
249 if (!service)
250 return;
251
252 qDeleteAll(c: m_characteristics);
253 m_characteristics.clear();
254 emit characteristicsUpdated();
255
256 if (service->state() == QLowEnergyService::DiscoveryRequired) {
257 //! [les-service-3]
258 connect(sender: service, signal: &QLowEnergyService::stateChanged,
259 receiver: this, slot: &Device::serviceDetailsDiscovered);
260 service->discoverDetails();
261 setUpdate("Back\n(Discovering details...)");
262 //! [les-service-3]
263 return;
264 }
265
266 //discovery already done
267 const QList<QLowEnergyCharacteristic> chars = service->characteristics();
268 for (const QLowEnergyCharacteristic &ch : chars) {
269 auto cInfo = new CharacteristicInfo(ch);
270 m_characteristics.append(t: cInfo);
271 }
272
273 QTimer::singleShot(interval: 0, receiver: this, slot: &Device::characteristicsUpdated);
274}
275
276void Device::deviceConnected()
277{
278 setUpdate("Back\n(Discovering services...)");
279 connected = true;
280 //! [les-service-2]
281 controller->discoverServices();
282 //! [les-service-2]
283}
284
285void Device::errorReceived(QLowEnergyController::Error /*error*/)
286{
287 qWarning() << "Error: " << controller->errorString();
288 setUpdate(QString("Back\n(%1)").arg(a: controller->errorString()));
289}
290
291void Device::setUpdate(const QString &message)
292{
293 m_message = message;
294 emit updateChanged();
295}
296
297void Device::disconnectFromDevice()
298{
299 // UI always expects disconnect() signal when calling this signal
300 // TODO what is really needed is to extend state() to a multi value
301 // and thus allowing UI to keep track of controller progress in addition to
302 // device scan progress
303
304 if (controller->state() != QLowEnergyController::UnconnectedState)
305 controller->disconnectFromDevice();
306 else
307 deviceDisconnected();
308}
309
310void Device::deviceDisconnected()
311{
312 qWarning() << "Disconnect from device";
313 emit disconnected();
314}
315
316void Device::serviceDetailsDiscovered(QLowEnergyService::ServiceState newState)
317{
318 if (newState != QLowEnergyService::ServiceDiscovered) {
319 // do not hang in "Scanning for characteristics" mode forever
320 // in case the service discovery failed
321 // We have to queue the signal up to give UI time to even enter
322 // the above mode
323 if (newState != QLowEnergyService::DiscoveringServices) {
324 QMetaObject::invokeMethod(obj: this, member: "characteristicsUpdated",
325 type: Qt::QueuedConnection);
326 }
327 return;
328 }
329
330 auto service = qobject_cast<QLowEnergyService *>(object: sender());
331 if (!service)
332 return;
333
334
335
336 //! [les-chars]
337 const QList<QLowEnergyCharacteristic> chars = service->characteristics();
338 for (const QLowEnergyCharacteristic &ch : chars) {
339 auto cInfo = new CharacteristicInfo(ch);
340 m_characteristics.append(t: cInfo);
341 }
342 //! [les-chars]
343
344 emit characteristicsUpdated();
345}
346
347void Device::deviceScanError(QBluetoothDeviceDiscoveryAgent::Error error)
348{
349 if (error == QBluetoothDeviceDiscoveryAgent::PoweredOffError)
350 setUpdate("The Bluetooth adaptor is powered off, power it on before doing discovery.");
351 else if (error == QBluetoothDeviceDiscoveryAgent::InputOutputError)
352 setUpdate("Writing or reading from the device resulted in an error.");
353 else {
354 static QMetaEnum qme = discoveryAgent->metaObject()->enumerator(
355 index: discoveryAgent->metaObject()->indexOfEnumerator(name: "Error"));
356 setUpdate("Error: " + QLatin1String(qme.valueToKey(value: error)));
357 }
358
359 m_deviceScanState = false;
360 emit devicesUpdated();
361 emit stateChanged();
362}
363
364bool Device::state()
365{
366 return m_deviceScanState;
367}
368
369bool Device::hasControllerError() const
370{
371 return (controller && controller->error() != QLowEnergyController::NoError);
372}
373
374bool Device::isRandomAddress() const
375{
376 return randomAddress;
377}
378
379void Device::setRandomAddress(bool newValue)
380{
381 randomAddress = newValue;
382 emit randomAddressChanged();
383}
384

source code of qtconnectivity/examples/bluetooth/lowenergyscanner/device.cpp