summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qlowenergyservice.cpp
blob: 0ec92d456e80b4699c91a8a775a08df0a78ef1b3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com>
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtBluetooth module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtCore/QCoreApplication>
#include <QtCore/QPointer>
#include <QtBluetooth/QLowEnergyService>

#include <algorithm>

#include "qlowenergycontroller_p.h"
#include "qlowenergyserviceprivate_p.h"

QT_BEGIN_NAMESPACE

/*!
    \class QLowEnergyService
    \inmodule QtBluetooth
    \brief The QLowEnergyService class represents an individual service
    on a Bluetooth Low Energy Device.

    \since 5.4

    QLowEnergyService provides access to the details of Bluetooth Low Energy
    services. The class facilitates the discovery and publification of
    service details, permits reading and writing of the contained data
    and notifies about data changes.

    \section1 Service Structure

    A Bluetooth Low Energy peripheral device can contain multiple services.
    In turn each service may include further services. This class represents a
    single service of the peripheral device and is created via
    \l QLowEnergyController::createServiceObject(). The \l type() indicates
    whether this service is a primary (top-level) service or whether the
    service is part of another service. Each service may contain one or more
    characteristics and each characteristic may contain descriptors. The
    resulting structure may look like the following diagram:

    \image peripheral-structure.png Structure of a generic peripheral

    A characteristic is the principle information carrier. It has a
    \l {QLowEnergyCharacteristic::value()}{value()} and
    \l {QLowEnergyCharacteristic::value()}{properties()}
    describing the access permissions for the value. The general purpose
    of the contained descriptor is to further define the nature of the
    characteristic. For example, it might specify how the value is meant to be
    interpreted or whether it can notify the value consumer about value
    changes.

    \section1 Service Interaction

    Once a service object was created for the first time, its details are yet to
    be discovered. This is indicated by its current \l state() being \l DiscoveryRequired.
    It is only possible to retrieve the \l serviceUuid() and \l serviceName().

    The discovery of its included services, characteristics and descriptors
    is triggered when calling \l discoverDetails(). During the discovery the
    \l state() transitions from \l DiscoveryRequired via \l DiscoveringServices
    to its final \l ServiceDiscovered state. This transition is advertised via
    the \l stateChanged() signal. Once the details are known, all of the contained
    characteristics, descriptors and included services are known and can be read
    or written.

    The values of characteristics and descriptors can be retrieved via
    \l QLowEnergyCharacteristic and \l QLowEnergyDescriptor, respectively.
    However writing those attributes requires the service object. The
    \l writeCharacteristic() function attempts to write a new value to the given
    characteristic. If the write attempt is successful, the \l characteristicWritten()
    signal is emitted. A failure to write triggers the \l CharacteristicWriteError.
    Writing a descriptor follows the same pattern.

    \note Currently, it is not possible to send signed write or reliable write requests.

    \target notifications

    In some cases the peripheral generates value updates which
    the central is interested in receiving. In order for a characteristic to support
    such notifications it must have the \l QLowEnergyCharacteristic::Notify or
    \l QLowEnergyCharacteristic::Indicate property and a descriptor of type
    \l QBluetoothUuid::ClientCharacteristicConfiguration. Provided those conditions
    are fulfilled notifications can be enabled as shown in the following code segment:

    \snippet doc_src_qtbluetooth.cpp enable_btle_notifications

    The example shows a battery level characteristic which updates the central
    on every value change. The notifications are provided via
    the \l characteristicChanged() signal. More details about this mechanism
    are provided by the
    \l {https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml}{Bluetooth Specification}.

    \section1 Service Data Sharing

    Each QLowEnergyService instance shares its internal states and information
    with other QLowEnergyService instance of the same service. If one instance
    initiates the discovery of the service details, all remaining instances
    automatically follow. Therefore the following snippet always works:

    \snippet doc_src_qtbluetooth.cpp data_share_qlowenergyservice

    Other operations such as calls to \l writeCharacteristic(),
    writeDescriptor() or the invalidation of the service due to the
    related \l QLowEnergyController disconnecting from the device are shared
    the same way.

    \note This class is provided by Qt 5.4 as part of a Bluetooth Low Energy Tech Preview.
    Some API elements may change until the final release of the feature.

    \sa QLowEnergyController, QLowEnergyCharacteristic, QLowEnergyDescriptor
 */

/*!
    \enum QLowEnergyService::ServiceType

    This enum describes the type of the service.

    \value PrimaryService       The service is a top-level/primary service.
                                If this type flag is not set, the service is considered
                                to be a secondary service. Each service may be included
                                by another service which is indicated by IncludedService.
    \value IncludedService      The service is included by another service.
*/

/*!
    \enum QLowEnergyService::ServiceError

    This enum describes all possible error conditions during the service's
    existence. The \l error() function returns the last occurred error.

    \value NoError                  No error has occurred.
    \value OperationError           An operation was attempted while the service was not ready.
                                    An example might be the attempt to write to
                                    the service while it was not yet in the
                                    \l ServiceDiscovered \l state() or the service is invalid
                                    due to a loss of connection to the peripheral device.
    \value CharacteristicWriteError An attempt to write a new value to a characteristic
                                    failed. For example, it might be triggered when attempting
                                    to write to a read-only characteristic.
    \value DescriptorWriteError     An attempt to write a new value to a descriptor
                                    failed. For example, it might be triggered when attempting
                                    to write to a read-only descriptor.
 */

/*!
    \enum QLowEnergyService::ServiceState

    This enum describes the \l state() of the service object.

    \value InvalidService       A service can become invalid when it looses
                                the connection to the underlying device. Even though
                                the connection may be lost it retains its last information.
                                An invalid service cannot become valid anymore even if
                                the connection to the device is re-established.
    \value DiscoveryRequired    The service details are yet to be discovered by calling \l discoverDetails().
                                The only reliable pieces of information are its
                                \l serviceUuid() and \l serviceName().
    \value DiscoveringServices  The service details are being discovered.
    \value ServiceDiscovered    The service details have been discovered.
 */

/*!
  \enum QLowEnergyService::WriteMode

  This enum describes the mode to be used when writing a characteristic value.
  The characteristic advertises its supported write modes via its
  \l {QLowEnergyCharacteristic::properties()}{properties}.

  \value WriteWithResponse      If a characteristic is written using this mode, the peripheral
                                shall send a write confirmation. If the operation is
                                successful, the confirmation is emitted via the
                                \l characteristicWritten() signal. Otherwise the
                                \l CharacteristicWriteError is emitted.
                                A characteristic must have set the
                                \l QLowEnergyCharacteristic::Write property to support this
                                write mode.

  \value WriteWithoutResponse   If a characteristic is written using this mode, the remote peripheral
                                shall not send a write confirmation. The operation's success
                                cannot be determined and the payload must not be longer than 20 bytes.
                                A characteristic must have set the
                                \l QLowEnergyCharacteristic::WriteNoResponse property to support this
                                write mode. Its adavantage is a quicker
                                write operation as it may happen in between other
                                device interactions.
 */

/*!
    \fn void QLowEnergyService::stateChanged(QLowEnergyService::ServiceState newState)

    This signal is emitted when the service's state changes. The \a newState can also be
    retrieved via \l state().

    \sa state()
 */

/*!
    \fn void QLowEnergyService::error(QLowEnergyService::ServiceError newError)

    This signal is emitted when an error occurrs. The \a newError parameter
    describes the error that occurred.
 */

/*!
    \fn void QLowEnergyService::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);

    This signal is emitted when the value of \a characteristic
    is successfully changed to \a newValue. The change must have been triggered
    by calling \l writeCharacteristic(). If the write operation is not successful,
    the \l error() signal is emitted using the \l CharacteristicWriteError flag.

    \note If \l writeCharacteristic() is called using the \l WriteWithoutResponse mode,
    this signal and the \l error() are never emitted.

    \sa writeCharacteristic()
 */

/*!
    \fn void QLowEnergyService::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);

    This signal is emitted when the value of \a characteristic is changed
    by an event on the peripheral. The \a newValue parameter contains the
    updated value of the \a characteristic.

    The signal emission implies that change notifications must
    have been activated via the characteristic's
    \l {QBluetoothUuid::ClientCharacteristicConfiguration}{ClientCharacteristicConfiguration}
    descriptor prior to the change event on the peripheral. More details on how this might be
    done can be found further \l{notifications}{above}.
 */

/*!
    \fn void QLowEnergyService::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)

    This signal is emitted when the value of \a descriptor
    is successfully changed to \a newValue. The change must have been caused
    by calling \l writeDescriptor().

    \sa writeDescriptor()
 */

/*!
  \internal

  QLowEnergyControllerPrivate creates instances of this class.
  The user gets access to class instances via
  \l QLowEnergyController::services().
 */
QLowEnergyService::QLowEnergyService(QSharedPointer<QLowEnergyServicePrivate> p,
                                     QObject *parent)
    : QObject(parent),
      d_ptr(p)
{
    qRegisterMetaType<QLowEnergyService::ServiceState>("QLowEnergyService::ServiceState");
    qRegisterMetaType<QLowEnergyService::ServiceError>("QLowEnergyService::ServiceError");

    connect(p.data(), SIGNAL(error(QLowEnergyService::ServiceError)),
            this, SIGNAL(error(QLowEnergyService::ServiceError)));
    connect(p.data(), SIGNAL(stateChanged(QLowEnergyService::ServiceState)),
            this, SIGNAL(stateChanged(QLowEnergyService::ServiceState)));
    connect(p.data(), SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
            this, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)));
    connect(p.data(), SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)),
            this, SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)));
    connect(p.data(), SIGNAL(descriptorWritten(QLowEnergyDescriptor,QByteArray)),
            this, SIGNAL(descriptorWritten(QLowEnergyDescriptor,QByteArray)));
}

/*!
    Destroys the \l QLowEnergyService instance.
 */
QLowEnergyService::~QLowEnergyService()
{
}

/*!
    Returns the UUIDs of all services which are included by the
    current service.

    It is possible that an included service contains yet another service. Such
    second level includes have to be obtained via their relevant first level
    QLowEnergyService instance. Technically, this could create
    a circular dependency.

    \l {QLowEnergyController::createServiceObject()} should be used to obtain
    service instances for each of the UUIDs.

    \sa createServiceObject()
 */
QList<QBluetoothUuid> QLowEnergyService::includedServices() const
{
    return d_ptr->includedServices;
}

/*!
    Returns the current state of the service.

    If the device's service was instantiated for the first time, the object's
    state is \l DiscoveryRequired. The state of all service objects which
    point to the same service on the peripheral device are always equal.
    This is caused by the shared nature of the internal object data.
    Therefore any service object instance created after
    the first one has a state equal to already existing instances.


    A service becomes invalid if the \l QLowEnergyController disconnects
    from the remote device. An invalid service retains its internal state
    at the time of the disconnect event. This implies that once the service
    details are discovered they can even be retrieved from an invalid
    service. This permits scenarios where the device connection is established,
    the service details are retrieved and the device immediately disconnected
    to permit the next device to connect to the peripheral device.

    However, under normal circumstances the connection should remain to avoid
    repeated discovery of services and their details. The discovery may take
    a while and the client can subscribe to ongoing change notifications.

    \sa stateChanged()
 */
QLowEnergyService::ServiceState QLowEnergyService::state() const
{
    return d_ptr->state;
}

/*!
    Returns the type of the service.
 */
QLowEnergyService::ServiceTypes QLowEnergyService::type() const
{
    return d_ptr->type;
}

/*!
    Returns the matching characteristic for \a uuid; otherwise an invalid
    characteristic.

    \sa characteristics()
*/
QLowEnergyCharacteristic QLowEnergyService::characteristic(const QBluetoothUuid &uuid) const
{
    foreach (const QLowEnergyHandle handle, d_ptr->characteristicList.keys()) {
        if (d_ptr->characteristicList[handle].uuid == uuid)
            return QLowEnergyCharacteristic(d_ptr, handle);
    }

    return QLowEnergyCharacteristic();
}

/*!
    Returns all characteristics associated with this \c QLowEnergyService instance.

    The returned list is empty if this service instance's \l discoverDetails()
    was not yet called or there are no known characteristics.

    \sa characteristic(), state(), discoverDetails()
*/

QList<QLowEnergyCharacteristic> QLowEnergyService::characteristics() const
{
    QList<QLowEnergyCharacteristic> results;
    QList<QLowEnergyHandle> handles = d_ptr->characteristicList.keys();
    std::sort(handles.begin(), handles.end());

    foreach (const QLowEnergyHandle &handle, handles) {
        QLowEnergyCharacteristic characteristic(d_ptr, handle);
        results.append(characteristic);
    }
    return results;
}


/*!
    Returns the UUID of the service; otherwise a null UUID.
 */
QBluetoothUuid QLowEnergyService::serviceUuid() const
{
    return d_ptr->uuid;
}


/*!
    Returns the name of the service; otherwise an empty string.

    The returned name can only be retrieved if \l serviceUuid()
    is a \l {https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx}{well-known UUID}.
*/
QString QLowEnergyService::serviceName() const
{
    bool ok = false;
    quint16 clsId = d_ptr->uuid.toUInt16(&ok);
    if (ok) {
        QBluetoothUuid::ServiceClassUuid id
                = static_cast<QBluetoothUuid::ServiceClassUuid>(clsId);
        const QString name = QBluetoothUuid::serviceClassToString(id);
        if (!name.isEmpty())
            return name;
    }
    return qApp ?
           qApp->translate("QBluetoothServiceDiscoveryAgent", "Unknown Service") :
           QStringLiteral("Unknown Service");
}


/*!
    Initiates the discovery of the services, characteristics
    and descriptors contained by the service. The discovery process is indicated
    via the \l stateChanged() signal.

    \sa state()
 */
void QLowEnergyService::discoverDetails()
{
    Q_D(QLowEnergyService);

    if (!d->controller || d->state == QLowEnergyService::InvalidService) {
        d->setError(QLowEnergyService::OperationError);
        return;
    }

    if (d->state != QLowEnergyService::DiscoveryRequired)
        return;

    d->setState(QLowEnergyService::DiscoveringServices);

    d->controller->discoverServiceDetails(d->uuid);
}

/*!
    Returns the last occurred error or \l NoError.
 */
QLowEnergyService::ServiceError QLowEnergyService::error() const
{
    return d_ptr->lastError;
}

/*!
    Returns \c true if \a characteristic belongs to this service;
    otherwise \c false.

    A characteristic belongs to a service if \l characteristics()
    contains the \a characteristic.
 */
bool QLowEnergyService::contains(const QLowEnergyCharacteristic &characteristic) const
{
    if (characteristic.d_ptr.isNull() || !characteristic.data)
        return false;

    if (d_ptr == characteristic.d_ptr
        && d_ptr->characteristicList.contains(characteristic.attributeHandle())) {
        return true;
    }

    return false;
}

/*!
    Writes \a newValue as value for the \a characteristic. If the operation is successful,
    the \l characteristicWritten() signal is emitted; otherwise the \l CharacteristicWriteError
    is set.

    The \a mode parameter determines whether the remote device should send a write
    confirmation. The to-be-written \a characteristic must support the relevant
    write mode. The characteristic's supported write modes are indicated by its
    \l QLowEnergyCharacteristic::Write and \l QLowEnergyCharacteristic::WriteNoResponse
    properties.

    \note Currently, it is not possible to use signed or reliable writes as defined by the
    Bluetooth specification.

    A characteristic can only be written if this service is in the \l ServiceDiscovered state,
    belongs to the service and is writable.

    \sa QLowEnergyCharacteristic::properties()
 */
void QLowEnergyService::writeCharacteristic(
        const QLowEnergyCharacteristic &characteristic,
        const QByteArray &newValue, QLowEnergyService::WriteMode mode)
{
    //TODO check behavior when writing to WriteSigned characteristic
    Q_D(QLowEnergyService);

    // not a characteristic of this service
    if (!contains(characteristic))
        return;

    if (state() != ServiceDiscovered)
        d->setError(QLowEnergyService::OperationError);

    if (!d->controller)
        return;

    // don't write if properties don't permit it
    if (mode == WriteWithResponse
            && (characteristic.properties() & QLowEnergyCharacteristic::Write))
    {
        d->controller->writeCharacteristic(characteristic.d_ptr,
                                       characteristic.attributeHandle(),
                                       newValue,
                                       true);
    } else if (mode == WriteWithoutResponse
               && (characteristic.properties() & QLowEnergyCharacteristic::WriteNoResponse)) {
        d->controller->writeCharacteristic(characteristic.d_ptr,
                                       characteristic.attributeHandle(),
                                       newValue,
                                       false);
    } else {
        d->setError(QLowEnergyService::OperationError);
    }
}

/*!
    Returns \c true if \a descriptor belongs to this service; otherwise \c false.
 */
bool QLowEnergyService::contains(const QLowEnergyDescriptor &descriptor) const
{
    if (descriptor.d_ptr.isNull() || !descriptor.data)
        return false;

    const QLowEnergyHandle charHandle = descriptor.characteristicHandle();
    if (!charHandle)
        return false;

    if (d_ptr == descriptor.d_ptr
        && d_ptr->characteristicList.contains(charHandle)
        && d_ptr->characteristicList[charHandle].descriptorList.contains(descriptor.handle()))
    {
        return true;
    }

    return false;
}

/*!
    Writes \a newValue as value for \a descriptor. If the operation is successful,
    the \l descriptorWritten() signal is emitted; otherwise the \l DescriptorWriteError
    is emitted.

    A descriptor can only be written if this service is in the \l ServiceDiscovered state,
    belongs to the service and is writable.
 */
void QLowEnergyService::writeDescriptor(const QLowEnergyDescriptor &descriptor,
                                        const QByteArray &newValue)
{
    Q_D(QLowEnergyService);

    if (!contains(descriptor))
        return;

    if (state() != ServiceDiscovered || !d->controller) {
        d->setError(QLowEnergyService::OperationError);
        return;
    }

    d->controller->writeDescriptor(descriptor.d_ptr,
                                   descriptor.characteristicHandle(),
                                   descriptor.handle(),
                                   newValue);
}

QT_END_NAMESPACE