1// Copyright (C) 2016 BlackBerry Limited. All rights reserved.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qlowenergycharacteristic.h"
5#include "qlowenergyserviceprivate_p.h"
6#include <QHash>
7
8QT_BEGIN_NAMESPACE
9
10QT_IMPL_METATYPE_EXTERN(QLowEnergyCharacteristic)
11
12/*!
13 \class QLowEnergyCharacteristic
14 \inmodule QtBluetooth
15 \brief The QLowEnergyCharacteristic class stores information about a Bluetooth
16 Low Energy service characteristic.
17
18 \since 5.4
19
20 QLowEnergyCharacteristic provides information about a Bluetooth Low Energy
21 service characteristic's name(), uuid(), value(), properties(), and
22 descriptors(). To obtain the characteristic's specification
23 and information, it is necessary to connect to the device using the
24 QLowEnergyService and QLowEnergyController classes.
25
26 The characteristic value may be written via the \l QLowEnergyService instance
27 that manages the service to which this characteristic belongs. The
28 \l {QLowEnergyService::writeCharacteristic()} function writes the new value.
29 The \l {QLowEnergyService::characteristicWritten()} signal is emitted upon success.
30 The value() of this object is automatically updated accordingly.
31
32 Characteristics may contain none, one or more descriptors. They can be individually
33 retrieved using the \l descriptor() function. The \l descriptors() function returns
34 all descriptors as a list. The general purpose of a descriptor is to add contextual
35 information to the characteristic. For example, the descriptor might provide
36 format or range information specifying how the characteristic's value is to be\
37 interpreted.
38
39 \sa QLowEnergyService, QLowEnergyDescriptor
40*/
41
42/*!
43 \enum QLowEnergyCharacteristic::PropertyType
44
45 This enum describes the properties of a characteristic.
46
47 \value Unknown The type is not known.
48 \value Broadcasting Allow for the broadcasting of Generic Attributes (GATT) characteristic values.
49 \value Read Allow the characteristic values to be read.
50 \value WriteNoResponse Allow characteristic values without responses to be written.
51 \value Write Allow for characteristic values to be written.
52 \value Notify Permits notification of characteristic values.
53 \value Indicate Permits indications of characteristic values.
54 \value WriteSigned Permits signed writes of the GATT characteristic values.
55 \value ExtendedProperty Additional characteristic properties are defined in the characteristic's
56 extended properties descriptor.
57
58 It is not recommended to set both Notify and Indicate properties on the same characteristic
59 as the underlying Bluetooth stack behaviors differ from platform to platform. Please see
60 \l QLowEnergyCharacteristic::clientCharacteristicConfiguration
61
62
63 \sa properties()
64*/
65
66struct QLowEnergyCharacteristicPrivate
67{
68 QLowEnergyHandle handle;
69};
70
71/*!
72 Construct a new QLowEnergyCharacteristic. A default-constructed instance of
73 this class is always invalid.
74
75 \sa isValid()
76*/
77QLowEnergyCharacteristic::QLowEnergyCharacteristic():
78 d_ptr(nullptr)
79{
80
81}
82
83/*!
84 Construct a new QLowEnergyCharacteristic that is a copy of \a other.
85
86 The two copies continue to share the same underlying data which does not detach
87 upon write.
88*/
89QLowEnergyCharacteristic::QLowEnergyCharacteristic(const QLowEnergyCharacteristic &other):
90 d_ptr(other.d_ptr)
91{
92 if (other.data) {
93 data = new QLowEnergyCharacteristicPrivate();
94 data->handle = other.data->handle;
95 }
96}
97
98/*!
99 \internal
100*/
101QLowEnergyCharacteristic::QLowEnergyCharacteristic(
102 QSharedPointer<QLowEnergyServicePrivate> p, QLowEnergyHandle handle):
103 d_ptr(p)
104{
105 data = new QLowEnergyCharacteristicPrivate();
106 data->handle = handle;
107}
108
109/*!
110 Destroys the QLowEnergyCharacteristic object.
111*/
112QLowEnergyCharacteristic::~QLowEnergyCharacteristic()
113{
114 delete data;
115}
116
117/*!
118 Returns the human-readable name of the characteristic.
119
120 The name is based on the characteristic's \l uuid() which must have been
121 standardized. The complete list of characteristic types can be found
122 under \l {https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicsHome.aspx}{Bluetooth.org Characteristics}.
123
124 The returned string is empty if the \l uuid() is unknown.
125
126 \sa QBluetoothUuid::characteristicToString()
127*/
128QString QLowEnergyCharacteristic::name() const
129{
130 return QBluetoothUuid::characteristicToString(
131 uuid: static_cast<QBluetoothUuid::CharacteristicType>(uuid().toUInt16()));
132}
133
134/*!
135 Returns the UUID of the characteristic if \l isValid() returns \c true; otherwise a
136 \l {QUuid::isNull()}{null} UUID.
137*/
138QBluetoothUuid QLowEnergyCharacteristic::uuid() const
139{
140 if (d_ptr.isNull() || !data
141 || !d_ptr->characteristicList.contains(key: data->handle))
142 return QBluetoothUuid();
143
144 return d_ptr->characteristicList[data->handle].uuid;
145}
146
147/*!
148 Returns the properties of the characteristic.
149
150 The properties define the access permissions for the characteristic.
151*/
152QLowEnergyCharacteristic::PropertyTypes QLowEnergyCharacteristic::properties() const
153{
154 if (d_ptr.isNull() || !data
155 || !d_ptr->characteristicList.contains(key: data->handle))
156 return QLowEnergyCharacteristic::Unknown;
157
158 return d_ptr->characteristicList[data->handle].properties;
159}
160
161/*!
162 Returns the cached value of the characteristic.
163
164 If the characteristic's \l properties() permit writing of new values,
165 the value can be updated using \l QLowEnergyService::writeCharacteristic().
166
167 The cache is updated during the associated service's
168 \l {QLowEnergyService::discoverDetails()} {detail discovery}, a successful
169 \l {QLowEnergyService::readCharacteristic()}{read}/\l {QLowEnergyService::writeCharacteristic()}{write}
170 operation or when an update notification is received.
171
172 The returned \l QByteArray always remains empty if the characteristic does not
173 have the \l {QLowEnergyCharacteristic::Read}{read permission}. In such cases only
174 the \l QLowEnergyService::characteristicChanged() or
175 \l QLowEnergyService::characteristicWritten() may provice information about the
176 value of this characteristic.
177*/
178QByteArray QLowEnergyCharacteristic::value() const
179{
180 if (d_ptr.isNull() || !data
181 || !d_ptr->characteristicList.contains(key: data->handle))
182 return QByteArray();
183
184 return d_ptr->characteristicList[data->handle].value;
185}
186
187/*!
188 \internal
189
190 Returns the handle of the characteristic's value attribute;
191 or \c 0 if the handle cannot be accessed on the platform or
192 if the characteristic is invalid.
193
194 \note On \macos and iOS handles can differ from 0, but these
195 values have no special meaning outside of internal/private API.
196*/
197QLowEnergyHandle QLowEnergyCharacteristic::handle() const
198{
199 if (d_ptr.isNull() || !data
200 || !d_ptr->characteristicList.contains(key: data->handle))
201 return 0;
202
203 return d_ptr->characteristicList[data->handle].valueHandle;
204}
205
206/*!
207 Makes a copy of \a other and assigns it to this \l QLowEnergyCharacteristic object.
208 The two copies continue to share the same service and controller details.
209*/
210QLowEnergyCharacteristic &QLowEnergyCharacteristic::operator=(const QLowEnergyCharacteristic &other)
211{
212 d_ptr = other.d_ptr;
213
214 if (!other.data) {
215 if (data) {
216 delete data;
217 data = nullptr;
218 }
219 } else {
220 if (!data)
221 data = new QLowEnergyCharacteristicPrivate();
222
223 data->handle = other.data->handle;
224 }
225 return *this;
226}
227
228/*!
229 \fn bool QLowEnergyCharacteristic::operator==(const QLowEnergyCharacteristic &a,
230 const QLowEnergyCharacteristic &b)
231 \brief Returns \c true if \a a is equal to \a b, otherwise \c false.
232
233 Two \l QLowEnergyCharacteristic instances are considered to be equal if they refer to
234 the same characteristic on the same remote Bluetooth Low Energy device or both instances
235 have been default-constructed.
236 */
237
238/*!
239 \fn bool QLowEnergyCharacteristic::operator!=(const QLowEnergyCharacteristic &a,
240 const QLowEnergyCharacteristic &b)
241 \brief Returns \c true if \a a and \a b are not equal; otherwise \c
242 false.
243
244 Two QLowEnergyCharcteristic instances are considered to be equal if they refer to
245 the same characteristic on the same remote Bluetooth Low Energy device or both instances
246 have been default-constructed.
247 */
248
249/*!
250 \brief Returns \c true if \a a is equal to \a b; otherwise \c false.
251 \internal
252
253 Two \l QLowEnergyCharacteristic instances are considered to be equal if they refer to
254 the same characteristic on the same remote Bluetooth Low Energy device or both instances
255 have been default-constructed.
256 */
257bool QLowEnergyCharacteristic::equals(const QLowEnergyCharacteristic &a,
258 const QLowEnergyCharacteristic &b)
259{
260 if (a.d_ptr != b.d_ptr)
261 return false;
262
263 if ((a.data && !b.data) || (!a.data && b.data))
264 return false;
265
266 if (!a.data)
267 return true;
268
269 if (a.data->handle != b.data->handle)
270 return false;
271
272 return true;
273}
274
275/*!
276 Returns \c true if the QLowEnergyCharacteristic object is valid, otherwise returns \c false.
277
278 An invalid characteristic object is not associated with any service (default-constructed)
279 or the associated service is no longer valid due to a disconnect from
280 the underlying Bluetooth Low Energy device, for example. Once the object is invalid
281 it cannot become valid anymore.
282
283 \note If a \l QLowEnergyCharacteristic instance turns invalid due to a disconnect
284 from the underlying device, the information encapsulated by the current
285 instance remains as it was at the time of the disconnect. Therefore it can be
286 retrieved after the disconnect event.
287*/
288bool QLowEnergyCharacteristic::isValid() const
289{
290 if (d_ptr.isNull() || !data)
291 return false;
292
293 if (d_ptr->state == QLowEnergyService::InvalidService)
294 return false;
295
296 return true;
297}
298
299/*!
300 \internal
301
302 Returns the handle of the characteristic or
303 \c 0 if the handle cannot be accessed on the platform or if the
304 characteristic is invalid.
305
306 \note On \macos and iOS handles can differ from 0, but these
307 values have no special meaning outside of internal/private API.
308
309 \sa isValid()
310 */
311QLowEnergyHandle QLowEnergyCharacteristic::attributeHandle() const
312{
313 if (d_ptr.isNull() || !data)
314 return 0;
315
316 return data->handle;
317}
318
319/*!
320 Returns the descriptor for \a uuid or an invalid \l QLowEnergyDescriptor instance.
321
322 \sa descriptors()
323*/
324QLowEnergyDescriptor QLowEnergyCharacteristic::descriptor(const QBluetoothUuid &uuid) const
325{
326 if (d_ptr.isNull() || !data)
327 return QLowEnergyDescriptor();
328
329 CharacteristicDataMap::const_iterator charIt = d_ptr->characteristicList.constFind(key: data->handle);
330 if (charIt != d_ptr->characteristicList.constEnd()) {
331 const QLowEnergyServicePrivate::CharData &charDetails = charIt.value();
332
333 DescriptorDataMap::const_iterator descIt = charDetails.descriptorList.constBegin();
334 for ( ; descIt != charDetails.descriptorList.constEnd(); ++descIt) {
335 const QLowEnergyHandle descHandle = descIt.key();
336 const QLowEnergyServicePrivate::DescData &descDetails = descIt.value();
337
338 if (descDetails.uuid == uuid)
339 return QLowEnergyDescriptor(d_ptr, data->handle, descHandle);
340 }
341 }
342
343 return QLowEnergyDescriptor();
344}
345
346/*!
347 Returns the Client Characteristic Configuration Descriptor or an
348 invalid \l QLowEnergyDescriptor instance if no
349 Client Characteristic Configuration Descriptor exists.
350
351 BTLE characteristics can support notifications and/or indications.
352 In both cases, the peripheral will inform the central on
353 each change of the characteristic's value. In the BTLE
354 attribute protocol, notification messages are not confirmed
355 by the central, while indications are confirmed.
356 Notifications are considered faster, but unreliable, while
357 indications are slower and more reliable.
358
359 If a characteristic supports notification or indication,
360 these can be enabled by writing special bit patterns to the
361 Client Characteristic Configuration Descriptor.
362 For convenience, these bit patterns are provided as
363 \l QLowEnergyCharacteristic::CCCDDisable,
364 \l QLowEnergyCharacteristic::CCCDEnableNotification, and
365 \l QLowEnergyCharacteristic::CCCDEnableIndication.
366
367 Enabling e.g. notification for a characteristic named
368 \c mycharacteristic in a service called \c myservice
369 could be done using the following code.
370 \code
371 auto cccd = mycharacteristic.clientCharacteristicConfiguration();
372 if (!cccd.isValid()) {
373 // your error handling
374 return error;
375 }
376 myservice->writeDescriptor(cccd, QLowEnergyCharacteristic::CCCDEnableNotification);
377 \endcode
378
379 \note
380 Calling \c characteristic.clientCharacteristicConfiguration() is equivalent to calling
381 \c characteristic.descriptor(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration).
382
383 \note
384 It is not recommended to use both notifications and indications on the same characteristic.
385 This applies to both server-side when setting up the characteristics, as well as client-side
386 when enabling them. The bluetooth stack behavior differs from platform to platform and the
387 cross-platform behavior will likely be inconsistent. As an example a Bluez Linux client might
388 unconditionally try to enable both mechanisms if both are supported, whereas a macOS client
389 might unconditionally enable just the notifications. If both are needed consider creating two
390 separate characteristics.
391
392 \since 6.2
393 \sa descriptor()
394*/
395QLowEnergyDescriptor QLowEnergyCharacteristic::clientCharacteristicConfiguration() const
396{
397 return descriptor(uuid: QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);
398}
399
400/*!
401 Returns the list of descriptors belonging to this characteristic; otherwise
402 an empty list.
403
404 \sa descriptor()
405*/
406QList<QLowEnergyDescriptor> QLowEnergyCharacteristic::descriptors() const
407{
408 QList<QLowEnergyDescriptor> result;
409
410 if (d_ptr.isNull() || !data
411 || !d_ptr->characteristicList.contains(key: data->handle))
412 return result;
413
414 QList<QLowEnergyHandle> descriptorKeys = d_ptr->characteristicList[data->handle].
415 descriptorList.keys();
416
417 std::sort(first: descriptorKeys.begin(), last: descriptorKeys.end());
418
419 for (const QLowEnergyHandle descHandle : std::as_const(t&: descriptorKeys)) {
420 QLowEnergyDescriptor descriptor(d_ptr, data->handle, descHandle);
421 result.append(t: descriptor);
422 }
423
424 return result;
425}
426
427/*!
428 \variable QLowEnergyCharacteristic::CCCDDisable
429 \since 6.2
430
431 Bit pattern to write into Client Characteristic Configuration Descriptor
432 to disable both notification and indication.
433
434 \sa QLowEnergyCharacteristic::clientCharacteristicConfiguration
435*/
436/*!
437 \variable QLowEnergyCharacteristic::CCCDEnableNotification
438 \since 6.2
439
440 Bit pattern to write into Client Characteristic Configuration Descriptor
441 to enable notification.
442
443 \sa QLowEnergyCharacteristic::clientCharacteristicConfiguration
444*/
445/*!
446 \variable QLowEnergyCharacteristic::CCCDEnableIndication
447 \since 6.2
448
449 Bit pattern to write into Client Characteristic Configuration Descriptor
450 to enable indication.
451
452 \sa QLowEnergyCharacteristic::clientCharacteristicConfiguration
453*/
454const QByteArray QLowEnergyCharacteristic::CCCDDisable = QByteArray::fromHex(hexEncoded: "0000");
455const QByteArray QLowEnergyCharacteristic::CCCDEnableNotification = QByteArray::fromHex(hexEncoded: "0100");
456const QByteArray QLowEnergyCharacteristic::CCCDEnableIndication = QByteArray::fromHex(hexEncoded: "0200");
457
458QT_END_NAMESPACE
459

source code of qtconnectivity/src/bluetooth/qlowenergycharacteristic.cpp