1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2016 Javier S. Pedro <maemo@javispedro.com>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include <QtCore/QCoreApplication>
6#include <QtCore/QPointer>
7#include <QtBluetooth/QLowEnergyService>
8
9#include <algorithm>
10
11#include "qlowenergycontrollerbase_p.h"
12#include "qlowenergyserviceprivate_p.h"
13
14#ifdef Q_OS_DARWIN
15#include "qlowenergycontroller_darwin_p.h"
16#endif // Q_OS_DARWIN
17
18QT_BEGIN_NAMESPACE
19
20QT_IMPL_METATYPE_EXTERN_TAGGED(QLowEnergyService::ServiceError, QLowEnergyService__ServiceError)
21QT_IMPL_METATYPE_EXTERN_TAGGED(QLowEnergyService::ServiceState, QLowEnergyService__ServiceState)
22QT_IMPL_METATYPE_EXTERN_TAGGED(QLowEnergyService::ServiceType, QLowEnergyService__ServiceType)
23QT_IMPL_METATYPE_EXTERN_TAGGED(QLowEnergyService::WriteMode, QLowEnergyService__WriteMode)
24
25/*!
26 \class QLowEnergyService
27 \inmodule QtBluetooth
28 \brief The QLowEnergyService class represents an individual service
29 on a Bluetooth Low Energy Device.
30
31 \since 5.4
32
33 QLowEnergyService provides access to the details of Bluetooth Low Energy
34 services. The class facilitates the discovery and publification of
35 service details, permits reading and writing of the contained data
36 and notifies about data changes.
37
38 \section1 Service Structure
39
40 A Bluetooth Low Energy peripheral device can contain multiple services.
41 In turn each service may include further services. This class represents a
42 single service of the peripheral device and is created via
43 \l QLowEnergyController::createServiceObject(). The \l type() indicates
44 whether this service is a primary (top-level) service or whether the
45 service is part of another service. Each service may contain one or more
46 characteristics and each characteristic may contain descriptors. The
47 resulting structure may look like the following diagram:
48
49 \image peripheral-structure.png Structure of a generic peripheral
50
51 A characteristic is the principal information carrier. It has a
52 \l {QLowEnergyCharacteristic::value()}{value()} and
53 \l {QLowEnergyCharacteristic::value()}{properties()}
54 describing the access permissions for the value. The general purpose
55 of the contained descriptor is to further define the nature of the
56 characteristic. For example, it might specify how the value is meant to be
57 interpreted or whether it can notify the value consumer about value
58 changes.
59
60 \section1 Service Interaction
61
62 Once a service object was created for the first time, its details are yet to
63 be discovered. This is indicated by its current \l state() being \l DiscoveryRequired.
64 It is only possible to retrieve the \l serviceUuid() and \l serviceName().
65
66 The discovery of its included services, characteristics and descriptors
67 is triggered when calling \l discoverDetails(). During the discovery the
68 \l state() transitions from \l DiscoveryRequired via \l DiscoveringService
69 to its final \l ServiceDiscovered state. This transition is advertised via
70 the \l stateChanged() signal. Once the details are known, all of the contained
71 characteristics, descriptors and included services are known and can be read
72 or written.
73
74 The values of characteristics and descriptors can be retrieved via
75 \l QLowEnergyCharacteristic and \l QLowEnergyDescriptor, respectively.
76 However, direct reading or writing of these attributes requires the service object.
77 The \l readCharacteristic() function attempts to re-read the value of a characteristic.
78 Although the initial service discovery may have obtained a value already this call may
79 be required in cases where the characteristic value constantly changes without
80 any notifications being provided. An example might be a time characteristic
81 that provides a continuous value. If the read attempt is successful, the
82 \l characteristicRead() signal is emitted. A failure to read the value triggers
83 the \l CharacteristicReadError.
84 The \l writeCharacteristic() function attempts to write a new value to the given
85 characteristic. If the write attempt is successful, the \l characteristicWritten()
86 signal is emitted. A failure to write triggers the \l CharacteristicWriteError.
87 Reading and writing of descriptors follows the same pattern.
88
89 Every attempt is made to read or write the value of a descriptor
90 or characteristic on the hardware. This means that meta information such as
91 \l QLowEnergyCharacteristic::properties() is generally ignored when reading and writing.
92 As an example, it is possible to call \l writeCharacteristic() despite the characteristic
93 being read-only based on its meta data description. The resulting write request is
94 forwarded to the connected device and it is up to the device to respond to the
95 potentially invalid request. In this case the result is the emission of the
96 \l CharacteristicWriteError in response to the returned device error. This behavior
97 simplifies interaction with devices which report wrong meta information.
98 If it was not possible to forward the request to the remote device the
99 \l OperationError is set. A potential reason could be that the to-be-written
100 characteristic object does not even belong the current service. In
101 summary, the two types of errors permit a quick distinction of local
102 and remote error cases.
103
104 All requests are serialised based on First-In First-Out principle.
105 For example, issuing a second write request, before the previous
106 write request has finished, is delayed until the first write request has finished.
107
108 \note Currently, it is not possible to send signed write or reliable write requests.
109
110 \target notifications
111
112 In some cases the peripheral generates value updates which
113 the central is interested in receiving. In order for a characteristic to support
114 such notifications it must have the \l QLowEnergyCharacteristic::Notify or
115 \l QLowEnergyCharacteristic::Indicate property and a descriptor of type
116 \l QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration. Provided those conditions
117 are fulfilled notifications can be enabled as shown in the following code segment:
118
119 \snippet doc_src_qtbluetooth.cpp enable_btle_notifications
120
121 The example shows a battery level characteristic which updates the central
122 on every value change. The notifications are provided via
123 the \l characteristicChanged() signal. More details about this mechanism
124 are provided by the
125 \l {https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml}{Bluetooth Specification}.
126
127 \section1 Service Data Sharing
128
129 Each QLowEnergyService instance shares its internal states and information
130 with other QLowEnergyService instance of the same service. If one instance
131 initiates the discovery of the service details, all remaining instances
132 automatically follow. Therefore the following snippet always works:
133
134 \snippet doc_src_qtbluetooth.cpp data_share_qlowenergyservice
135
136 Other operations such as calls to \l readCharacteristic(), \l readDescriptor(), \l writeCharacteristic(),
137 \l writeDescriptor() or the invalidation of the service due to the
138 related \l QLowEnergyController disconnecting from the device are shared
139 the same way.
140
141 \sa QLowEnergyController, QLowEnergyCharacteristic, QLowEnergyDescriptor
142 */
143
144/*!
145 \enum QLowEnergyService::ServiceType
146
147 This enum describes the type of the service.
148
149 \value PrimaryService The service is a top-level/primary service.
150 If this type flag is not set, the service is considered
151 to be a secondary service. Each service may be included
152 by another service which is indicated by IncludedService.
153 \value IncludedService The service is included by another service.
154 On some platforms, this flag cannot be determined until
155 the service that includes the current service was
156 discovered.
157*/
158
159/*!
160 \enum QLowEnergyService::ServiceError
161
162 This enum describes all possible error conditions during the service's
163 existence. The \l error() function returns the last occurred error.
164
165 \value NoError No error has occurred.
166 \value OperationError An operation was attempted while the service was not ready.
167 An example might be the attempt to write to
168 the service while it was not yet in the
169 \l ServiceDiscovered \l state() or the service is invalid
170 due to a loss of connection to the peripheral device.
171 \value [since 5.5] CharacteristicReadError An attempt to read a characteristic value failed.
172 For example, it might be triggered in response
173 to a call to \l readCharacteristic().
174 \value CharacteristicWriteError An attempt to write a new value to a characteristic
175 failed. For example, it might be triggered when attempting
176 to write to a read-only characteristic.
177 \value [since 5.5] DescriptorReadError An attempt to read a descriptor value failed.
178 For example, it might be triggered in response
179 to a call to \l readDescriptor().
180 \value DescriptorWriteError An attempt to write a new value to a descriptor
181 failed. For example, it might be triggered when attempting
182 to write to a read-only descriptor.
183 \value [since 5.5] UnknownError An unknown error occurred when interacting with the service.
184 */
185
186/*!
187 \enum QLowEnergyService::ServiceState
188
189 This enum describes the \l state() of the service object.
190
191 \value InvalidService A service can become invalid when it looses
192 the connection to the underlying device. Even though
193 the connection may be lost it retains its last information.
194 An invalid service cannot become valid anymore even if
195 the connection to the device is re-established.
196 \value RemoteService The service details are yet to be discovered
197 by calling \l discoverDetails().
198 The only reliable pieces of information are its
199 \l serviceUuid() and \l serviceName().
200 \value RemoteServiceDiscovering The service details are being discovered.
201 \value RemoteServiceDiscovered The service details have been discovered.
202 \value [since 5.7] LocalService The service is associated with a controller object in the
203 \l{QLowEnergyController::PeripheralRole}{peripheral role}.
204 Such service objects do not change their state.
205 \value DiscoveryRequired Deprecated. Was renamed to RemoteService.
206 \value DiscoveringService Deprecated. Was renamed to RemoteServiceDiscovering.
207 \value ServiceDiscovered Deprecated. Was renamed to RemoteServiceDiscovered.
208 */
209
210/*!
211 \enum QLowEnergyService::DiscoveryMode
212
213 This enum lists service discovery modes.
214 All modes discover the characteristics of the service and the descriptors
215 of the characteristics. The modes differ in whether characteristic values
216 and descriptors are read.
217
218 \value FullDiscovery During a full discovery, all characteristics
219 are discovered. All characteristic values and
220 descriptors are read.
221 \value SkipValueDiscovery During a minimal discovery, all characteristics
222 are discovered. Characteristic values and
223 descriptors are not read.
224
225 \sa discoverDetails()
226 \since 6.2
227*/
228
229/*!
230 \enum QLowEnergyService::WriteMode
231
232 This enum describes the mode to be used when writing a characteristic value.
233 The characteristic advertises its supported write modes via its
234 \l {QLowEnergyCharacteristic::properties()}{properties}.
235
236 \value WriteWithResponse If a characteristic is written using this mode, the peripheral
237 shall send a write confirmation. If the operation is
238 successful, the confirmation is emitted via the
239 \l characteristicWritten() signal. Otherwise the
240 \l CharacteristicWriteError is emitted.
241 A characteristic must have set the
242 \l QLowEnergyCharacteristic::Write property to support this
243 write mode.
244
245 \value WriteWithoutResponse If a characteristic is written using this mode, the remote peripheral
246 shall not send a write confirmation. The operation's success
247 cannot be determined and the payload must not be longer than 20 bytes.
248 A characteristic must have set the
249 \l QLowEnergyCharacteristic::WriteNoResponse property to support this
250 write mode. Its adavantage is a quicker
251 write operation as it may happen in between other
252 device interactions.
253
254 \value [since 5.7] WriteSigned If a characteristic is written using this mode, the remote
255 peripheral shall not send a write confirmation. The operation's
256 success cannot be determined and the payload must not be longer
257 than 8 bytes. A bond must exist between the two devices and the
258 link must not be encrypted.
259 A characteristic must have set the
260 \l QLowEnergyCharacteristic::WriteSigned property to support
261 this write mode.
262 This value is currently only supported on Android and on Linux
263 with BlueZ 5 and a kernel version 3.7 or newer.
264 */
265
266/*!
267 \fn void QLowEnergyService::stateChanged(QLowEnergyService::ServiceState newState)
268
269 This signal is emitted when the service's state changes. The \a newState can also be
270 retrieved via \l state().
271
272 \sa state()
273 */
274
275/*!
276 \fn void QLowEnergyService::errorOccurred(QLowEnergyService::ServiceError newError)
277
278 This signal is emitted when an error occurrs. The \a newError parameter
279 describes the error that occurred.
280
281 \since 6.2
282 */
283
284/*!
285 \fn void QLowEnergyService::characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &value)
286
287 This signal is emitted when the read request for \a characteristic successfully returned
288 its \a value. The signal might be triggered by calling \l characteristicRead(). If
289 the read operation is not successful, the \l errorOccurred() signal is emitted using the
290 \l CharacteristicReadError flag.
291
292 \note This signal is only emitted for Central Role related use cases.
293
294 \sa readCharacteristic()
295 \since 5.5
296 */
297
298/*!
299 \fn void QLowEnergyService::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
300
301 This signal is emitted when the value of \a characteristic
302 is successfully changed to \a newValue. The change must have been triggered
303 by calling \l writeCharacteristic(). If the write operation is not successful,
304 the \l errorOccurred() signal is emitted using the \l CharacteristicWriteError flag.
305
306 The reception of the written signal can be considered as a sign that the target device
307 received the to-be-written value and reports back the status of write request.
308
309 \note If \l writeCharacteristic() is called using the \l WriteWithoutResponse mode,
310 this signal and the \l errorOccurred() are never emitted.
311
312 \note This signal is only emitted for Central Role related use cases.
313
314 \sa writeCharacteristic()
315 */
316
317/*!
318 \fn void QLowEnergyService::characteristicChanged(const QLowEnergyCharacteristic
319 &characteristic, const QByteArray &newValue)
320
321 If the associated controller object is in the \l {QLowEnergyController::CentralRole}{central}
322 role, this signal is emitted when the value of \a characteristic is changed by an event on the
323 peripheral/device side. In that case, the signal emission implies that change notifications must
324 have been activated via the characteristic's
325 \l {QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration}{ClientCharacteristicConfiguration}
326 descriptor prior to the change event on the peripheral. More details on how this might be
327 done can be found further \l{notifications}{above}.
328
329 If the controller is in the \l {QLowEnergyController::PeripheralRole}{peripheral} role, that is,
330 the service object was created via \l QLowEnergyController::addService, the signal is emitted
331 when a GATT client has written the value of the characteristic using a write request or command.
332
333 The \a newValue parameter contains the updated value of the \a characteristic.
334
335 */
336
337/*!
338 \fn void QLowEnergyService::descriptorRead(const QLowEnergyDescriptor &descriptor, const QByteArray &value)
339
340 This signal is emitted when the read request for \a descriptor successfully returned
341 its \a value. The signal might be triggered by calling \l descriptorRead(). If
342 the read operation is not successful, the \l errorOccurred() signal is emitted using the
343 \l DescriptorReadError flag.
344
345 \note This signal is only emitted for Central Role related use cases.
346
347 \sa readDescriptor()
348 \since 5.5
349 */
350
351/*!
352 \fn void QLowEnergyService::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)
353
354 This signal is emitted when the value of \a descriptor
355 is successfully changed to \a newValue. If the associated controller object is in the
356 \l {QLowEnergyController::CentralRole}{central} role, the change must have been caused
357 by calling \l writeDescriptor(). Otherwise, the signal is the result of a write request or
358 command from a GATT client to the respective descriptor.
359
360 \sa writeDescriptor()
361 */
362
363/*!
364 \internal
365
366 QLowEnergyControllerPrivate creates instances of this class.
367 The user gets access to class instances via
368 \l QLowEnergyController::services().
369 */
370QLowEnergyService::QLowEnergyService(QSharedPointer<QLowEnergyServicePrivate> p,
371 QObject *parent)
372 : QObject(parent),
373 d_ptr(p)
374{
375 qRegisterMetaType<QLowEnergyService::ServiceState>();
376 qRegisterMetaType<QLowEnergyService::ServiceError>();
377 qRegisterMetaType<QLowEnergyService::ServiceType>();
378 qRegisterMetaType<QLowEnergyService::WriteMode>();
379
380 connect(sender: p.data(), signal: &QLowEnergyServicePrivate::errorOccurred, context: this,
381 slot: &QLowEnergyService::errorOccurred);
382 connect(sender: p.data(), signal: &QLowEnergyServicePrivate::stateChanged,
383 context: this, slot: &QLowEnergyService::stateChanged);
384 connect(sender: p.data(), signal: &QLowEnergyServicePrivate::characteristicChanged,
385 context: this, slot: &QLowEnergyService::characteristicChanged);
386 connect(sender: p.data(), signal: &QLowEnergyServicePrivate::characteristicWritten,
387 context: this, slot: &QLowEnergyService::characteristicWritten);
388 connect(sender: p.data(), signal: &QLowEnergyServicePrivate::descriptorWritten,
389 context: this, slot: &QLowEnergyService::descriptorWritten);
390 connect(sender: p.data(), signal: &QLowEnergyServicePrivate::characteristicRead,
391 context: this, slot: &QLowEnergyService::characteristicRead);
392 connect(sender: p.data(), signal: &QLowEnergyServicePrivate::descriptorRead,
393 context: this, slot: &QLowEnergyService::descriptorRead);
394}
395
396/*!
397 Destroys the \l QLowEnergyService instance.
398 */
399QLowEnergyService::~QLowEnergyService()
400{
401}
402
403/*!
404 Returns the UUIDs of all services which are included by the
405 current service.
406
407 The returned list is empty if this service instance's \l discoverDetails()
408 was not yet called or there are no known characteristics.
409
410 It is possible that an included service contains yet another service. Such
411 second level includes have to be obtained via their relevant first level
412 QLowEnergyService instance. Technically, this could create
413 a circular dependency.
414
415 \l {QLowEnergyController::createServiceObject()} should be used to obtain
416 service instances for each of the UUIDs.
417
418 \sa {QLowEnergyController::}{createServiceObject()}
419 */
420QList<QBluetoothUuid> QLowEnergyService::includedServices() const
421{
422 return d_ptr->includedServices;
423}
424
425/*!
426 Returns the current state of the service.
427
428 If the device's service was instantiated for the first time, the object's
429 state is \l DiscoveryRequired. The state of all service objects which
430 point to the same service on the peripheral device are always equal.
431 This is caused by the shared nature of the internal object data.
432 Therefore any service object instance created after
433 the first one has a state equal to already existing instances.
434
435 A service becomes invalid if the \l QLowEnergyController disconnects
436 from the remote device. An invalid service retains its internal state
437 at the time of the disconnect event. This implies that once the service
438 details are discovered they can even be retrieved from an invalid
439 service. This permits scenarios where the device connection is established,
440 the service details are retrieved and the device immediately disconnected
441 to permit the next device to connect to the peripheral device.
442
443 However, under normal circumstances the connection should remain to avoid
444 repeated discovery of services and their details. The discovery may take
445 a while and the client can subscribe to ongoing change notifications.
446
447 \sa stateChanged()
448 */
449QLowEnergyService::ServiceState QLowEnergyService::state() const
450{
451 return d_ptr->state;
452}
453
454/*!
455 Returns the type of the service.
456
457 \note The type attribute cannot be relied upon until the service has
458 reached the \l ServiceDiscovered state. This field is initialised
459 with \l PrimaryService.
460
461 \note On Android, it is not possible to determine whether a service
462 is a primary or secondary service. Therefore all services
463 have the \l PrimaryService flag set.
464
465 */
466QLowEnergyService::ServiceTypes QLowEnergyService::type() const
467{
468 return d_ptr->type;
469}
470
471/*!
472 Returns the matching characteristic for \a uuid; otherwise an invalid
473 characteristic.
474
475 The returned characteristic is invalid if this service instance's \l discoverDetails()
476 was not yet called or there are no characteristics with a matching \a uuid.
477
478 \sa characteristics()
479*/
480QLowEnergyCharacteristic QLowEnergyService::characteristic(const QBluetoothUuid &uuid) const
481{
482 CharacteristicDataMap::const_iterator charIt = d_ptr->characteristicList.constBegin();
483 for ( ; charIt != d_ptr->characteristicList.constEnd(); ++charIt) {
484 const QLowEnergyHandle charHandle = charIt.key();
485 const QLowEnergyServicePrivate::CharData &charDetails = charIt.value();
486
487 if (charDetails.uuid == uuid)
488 return QLowEnergyCharacteristic(d_ptr, charHandle);
489 }
490
491 return QLowEnergyCharacteristic();
492}
493
494/*!
495 Returns all characteristics associated with this \c QLowEnergyService instance.
496
497 The returned list is empty if this service instance's \l discoverDetails()
498 was not yet called or there are no known characteristics.
499
500 \sa characteristic(), state(), discoverDetails()
501*/
502
503QList<QLowEnergyCharacteristic> QLowEnergyService::characteristics() const
504{
505 QList<QLowEnergyCharacteristic> results;
506 QList<QLowEnergyHandle> handles = d_ptr->characteristicList.keys();
507 std::sort(first: handles.begin(), last: handles.end());
508
509 for (const QLowEnergyHandle &handle : std::as_const(t&: handles)) {
510 QLowEnergyCharacteristic characteristic(d_ptr, handle);
511 results.append(t: characteristic);
512 }
513 return results;
514}
515
516
517/*!
518 Returns the UUID of the service; otherwise a null UUID.
519 */
520QBluetoothUuid QLowEnergyService::serviceUuid() const
521{
522 return d_ptr->uuid;
523}
524
525
526/*!
527 Returns the name of the service; otherwise an empty string.
528
529 The returned name can only be retrieved if \l serviceUuid()
530 is a \l {https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx}{well-known UUID}.
531*/
532QString QLowEnergyService::serviceName() const
533{
534 bool ok = false;
535 quint16 clsId = d_ptr->uuid.toUInt16(ok: &ok);
536 if (ok) {
537 QBluetoothUuid::ServiceClassUuid id
538 = static_cast<QBluetoothUuid::ServiceClassUuid>(clsId);
539 const QString name = QBluetoothUuid::serviceClassToString(uuid: id);
540 if (!name.isEmpty())
541 return name;
542 }
543 return qApp ?
544 qApp->translate(context: "QBluetoothServiceDiscoveryAgent", key: "Unknown Service") :
545 QStringLiteral("Unknown Service");
546}
547
548/*!
549 Initiates discovery of the service's included services, characteristics,
550 and their associated descriptors.
551
552 The discovery process is indicated via the \l stateChanged() signal.
553 After creation, the service is in \l DiscoveryRequired state.
554 When calling discoverDetails() it transitions to \l DiscoveringService.
555 After completion of detail discovery, it transitions to
556 \l ServiceDiscovered state. On each transition, the \l stateChanged()
557 signal is emitted.
558 Depending on the argument \a mode, a \l FullDiscovery or a
559 \l SkipValueDiscovery is performed. In
560 any case, all services and characteristics are discovered. A
561 \l FullDiscovery proceeds and reads all characteristic values and
562 descriptors. A \l SkipValueDiscovery does not read characteristic values
563 and descriptors. A \l SkipValueDiscovery has two advantages. First, it is
564 faster. Second, it circumvents bugs in some devices which wrongly advertise
565 characteristics or descriptors as readable but nevertheless do not permit
566 reads on them. This can trigger unpredictable behavior.
567 After a \l SkipValueDiscovery, it is necessary to call
568 \l readCharacteristic() / \l readDescriptor() and wait for them to
569 finish successfully before accessing the value of a characteristic or
570 descriptor.
571
572 The argument \a mode was introduced in Qt 6.2.
573
574 \sa state()
575 */
576void QLowEnergyService::discoverDetails(DiscoveryMode mode)
577{
578 Q_D(QLowEnergyService);
579
580 if (!d->controller || d->state == QLowEnergyService::InvalidService) {
581 d->setError(QLowEnergyService::OperationError);
582 return;
583 }
584
585 if (d->state != QLowEnergyService::RemoteService)
586 return;
587
588 d->setState(QLowEnergyService::RemoteServiceDiscovering);
589
590 d->controller->discoverServiceDetails(service: d->uuid, mode);
591}
592
593/*!
594 Returns the last occurred error or \l NoError.
595 */
596QLowEnergyService::ServiceError QLowEnergyService::error() const
597{
598 return d_ptr->lastError;
599}
600
601/*!
602 Returns \c true if \a characteristic belongs to this service;
603 otherwise \c false.
604
605 A characteristic belongs to a service if \l characteristics()
606 contains the \a characteristic.
607 */
608bool QLowEnergyService::contains(const QLowEnergyCharacteristic &characteristic) const
609{
610 if (characteristic.d_ptr.isNull() || !characteristic.data)
611 return false;
612
613 if (d_ptr == characteristic.d_ptr
614 && d_ptr->characteristicList.contains(key: characteristic.attributeHandle())) {
615 return true;
616 }
617
618 return false;
619}
620
621
622/*!
623 Reads the value of \a characteristic. If the operation is successful, the
624 \l characteristicRead() signal is emitted; otherwise the \l CharacteristicReadError
625 is set. In general, a \a characteristic is readable, if its
626 \l QLowEnergyCharacteristic::Read property is set.
627
628 All descriptor and characteristic requests towards the same remote device are
629 serialised. A queue is employed when issuing multiple requests at the same time.
630 The queue does not eliminate duplicated read requests for the same characteristic.
631
632 A characteristic can only be read if the service is in the \l ServiceDiscovered state
633 and belongs to the service. If one of these conditions is
634 not true the \l QLowEnergyService::OperationError is set.
635
636 \note Calling this function despite \l QLowEnergyCharacteristic::properties() reporting a non-readable property
637 always attempts to read the characteristic's value on the hardware. If the hardware
638 returns with an error the \l CharacteristicReadError is set.
639
640 \sa characteristicRead(), writeCharacteristic()
641
642 \since 5.5
643 */
644void QLowEnergyService::readCharacteristic(
645 const QLowEnergyCharacteristic &characteristic)
646{
647 Q_D(QLowEnergyService);
648
649 if (d->controller == nullptr || state() != RemoteServiceDiscovered || !contains(characteristic)) {
650 d->setError(QLowEnergyService::OperationError);
651 return;
652 }
653
654 d->controller->readCharacteristic(service: characteristic.d_ptr,
655 charHandle: characteristic.attributeHandle());
656}
657
658/*!
659 Writes \a newValue as value for the \a characteristic. The exact semantics depend on
660 the role that the associated controller object is in.
661
662 \b {Central role}
663
664 The call results in a write request or command to a remote peripheral.
665 If the operation is successful,
666 the \l characteristicWritten() signal is emitted; otherwise the \l CharacteristicWriteError
667 is set. Calling this function does not trigger the a \l characteristicChanged()
668 signal unless the peripheral itself changes the value again after the current write request.
669
670 The \a mode parameter determines whether the remote device should send a write
671 confirmation. The to-be-written \a characteristic must support the relevant
672 write mode. The characteristic's supported write modes are indicated by its
673 \l QLowEnergyCharacteristic::Write and \l QLowEnergyCharacteristic::WriteNoResponse
674 properties.
675
676 All descriptor and characteristic write requests towards the same remote device are
677 serialised. A queue is employed when issuing multiple write requests at the same time.
678 The queue does not eliminate duplicated write requests for the same characteristic.
679 For example, if the same descriptor is set to the value A and immediately afterwards
680 to B, the two write request are executed in the given order.
681
682 \note Currently, it is not possible to use signed or reliable writes as defined by the
683 Bluetooth specification.
684
685 A characteristic can only be written if this service is in the \l ServiceDiscovered state
686 and belongs to the service. If one of these conditions is
687 not true the \l QLowEnergyService::OperationError is set.
688
689 \note Calling this function despite \l QLowEnergyCharacteristic::properties() reporting
690 a non-writable property always attempts to write to the hardware.
691 Similarly, a \l WriteWithoutResponse is sent to the hardware too although the
692 characteristic may only support \l WriteWithResponse. If the hardware returns
693 with an error the \l CharacteristicWriteError is set.
694
695 \b {Peripheral role}
696
697 The call results in the value of the characteristic getting updated in the local database.
698
699 If a client is currently connected and it has enabled notifications or indications for
700 the characteristic, the respective information will be sent.
701 If a device has enabled notifications or indications for the characteristic and that device
702 is currently not connected, but a bond exists between it and the local device, then
703 the notification or indication will be sent on the next reconnection.
704
705 If there is a constraint on the length of the characteristic value and \a newValue
706 does not adhere to that constraint, the behavior is unspecified.
707
708 \note The \a mode argument is ignored in peripheral mode.
709
710 \sa QLowEnergyService::characteristicWritten(), QLowEnergyService::readCharacteristic()
711
712 */
713void QLowEnergyService::writeCharacteristic(
714 const QLowEnergyCharacteristic &characteristic,
715 const QByteArray &newValue, QLowEnergyService::WriteMode mode)
716{
717 //TODO check behavior when writing to WriteSigned characteristic
718 Q_D(QLowEnergyService);
719
720 if (d->controller == nullptr
721 || (d->controller->role == QLowEnergyController::CentralRole
722 && state() != RemoteServiceDiscovered)
723 || !contains(characteristic)) {
724 d->setError(QLowEnergyService::OperationError);
725 return;
726 }
727
728 // don't write if properties don't permit it
729 d->controller->writeCharacteristic(service: characteristic.d_ptr,
730 charHandle: characteristic.attributeHandle(),
731 newValue,
732 writeMode: mode);
733}
734
735/*!
736 Returns \c true if \a descriptor belongs to this service; otherwise \c false.
737 */
738bool QLowEnergyService::contains(const QLowEnergyDescriptor &descriptor) const
739{
740 if (descriptor.d_ptr.isNull() || !descriptor.data)
741 return false;
742
743 const QLowEnergyHandle charHandle = descriptor.characteristicHandle();
744 if (!charHandle)
745 return false;
746
747 if (d_ptr == descriptor.d_ptr
748 && d_ptr->characteristicList.contains(key: charHandle)
749 && d_ptr->characteristicList[charHandle].descriptorList.contains(key: descriptor.handle()))
750 {
751 return true;
752 }
753
754 return false;
755}
756
757/*!
758 Reads the value of \a descriptor. If the operation is successful, the
759 \l descriptorRead() signal is emitted; otherwise the \l DescriptorReadError
760 is set.
761
762 All descriptor and characteristic requests towards the same remote device are
763 serialised. A queue is employed when issuing multiple requests at the same time.
764 The queue does not eliminate duplicated read requests for the same descriptor.
765
766 A descriptor can only be read if the service is in the \l ServiceDiscovered state
767 and the descriptor belongs to the service. If one of these conditions is
768 not true the \l QLowEnergyService::OperationError is set.
769
770 \sa descriptorRead(), writeDescriptor()
771
772 \since 5.5
773 */
774void QLowEnergyService::readDescriptor(
775 const QLowEnergyDescriptor &descriptor)
776{
777 Q_D(QLowEnergyService);
778
779 if (d->controller == nullptr || state() != RemoteServiceDiscovered || !contains(descriptor)) {
780 d->setError(QLowEnergyService::OperationError);
781 return;
782 }
783
784 d->controller->readDescriptor(service: descriptor.d_ptr,
785 charHandle: descriptor.characteristicHandle(),
786 descriptorHandle: descriptor.handle());
787}
788
789/*!
790 Writes \a newValue as value for \a descriptor. The exact semantics depend on
791 the role that the associated controller object is in.
792
793 \b {Central role}
794
795 A call to this function results in a write request to the remote device.
796 If the operation is successful, the \l descriptorWritten() signal is emitted; otherwise
797 the \l DescriptorWriteError is emitted.
798
799 All descriptor and characteristic requests towards the same remote device are
800 serialised. A queue is employed when issuing multiple write requests at the same time.
801 The queue does not eliminate duplicated write requests for the same descriptor.
802 For example, if the same descriptor is set to the value A and immediately afterwards
803 to B, the two write request are executed in the given order.
804
805 A descriptor can only be written if this service is in the \l ServiceDiscovered state,
806 belongs to the service. If one of these conditions is
807 not true the \l QLowEnergyService::OperationError is set.
808
809 \b {Peripheral Role}
810
811 The value is written to the local service database. If the contents of \a newValue are not
812 valid for \a descriptor, the behavior is unspecified.
813
814 \sa descriptorWritten(), readDescriptor()
815 */
816void QLowEnergyService::writeDescriptor(const QLowEnergyDescriptor &descriptor,
817 const QByteArray &newValue)
818{
819 Q_D(QLowEnergyService);
820
821 if (d->controller == nullptr
822 || (d->controller->role == QLowEnergyController::CentralRole
823 && state() != RemoteServiceDiscovered)
824 || !contains(descriptor)) {
825 d->setError(QLowEnergyService::OperationError);
826 return;
827 }
828#ifdef Q_OS_DARWIN
829 if (descriptor.uuid() == QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration) {
830 // We have to identify a special case - ClientCharacteristicConfiguration
831 // since with CoreBluetooth:
832 //
833 // "You cannot use this method to write the value of a client configuration descriptor
834 // (represented by the CBUUIDClientCharacteristicConfigurationString constant),
835 // which describes how notification or indications are configured for a
836 // characteristic’s value with respect to a client. If you want to manage
837 // notifications or indications for a characteristic’s value, you must
838 // use the setNotifyValue:forCharacteristic: method instead."
839 auto controller = static_cast<QLowEnergyControllerPrivateDarwin *>(d->controller.data());
840 return controller->setNotifyValue(descriptor.d_ptr, descriptor.characteristicHandle(), newValue);
841 }
842#endif // Q_OS_DARWIN
843
844 d->controller->writeDescriptor(service: descriptor.d_ptr,
845 charHandle: descriptor.characteristicHandle(),
846 descriptorHandle: descriptor.handle(),
847 newValue);
848}
849
850QT_END_NAMESPACE
851
852#include "moc_qlowenergyservice.cpp"
853

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