Line data Source code
1 : // SPDX-FileCopyrightText: 2022-2025 Paul Colby <git@colby.id.au>
2 : // SPDX-License-Identifier: LGPL-3.0-or-later
3 :
4 : /*!
5 : * \file
6 : * Defines the AbstractPokitService and AbstractPokitServicePrivate classes.
7 : */
8 :
9 : #include <qtpokit/abstractpokitservice.h>
10 : #include "abstractpokitservice_p.h"
11 : #include "pokitproducts_p.h"
12 : #include "../stringliterals_p.h"
13 :
14 : #include <qtpokit/pokitdevice.h>
15 :
16 : #include <QLowEnergyController>
17 :
18 : QTPOKIT_BEGIN_NAMESPACE
19 : DOKIT_USE_STRINGLITERALS
20 :
21 : /*!
22 : * \class AbstractPokitService
23 : *
24 : * The AbstractPokitService class provides a common base for Pokit services classes.
25 : */
26 :
27 : /*!
28 : * \cond internal
29 : * Constructs a new Pokit service with \a parent, and private implementation \a d.
30 : */
31 17595 : AbstractPokitService::AbstractPokitService(
32 17595 : AbstractPokitServicePrivate * const d, QObject * const parent)
33 28357 : : QObject(parent), d_ptr(d)
34 12922 : {
35 :
36 30517 : }
37 : /// \endcond
38 :
39 : /*!
40 : * Destroys this AbstractPokitService object.
41 : */
42 6570 : AbstractPokitService::~AbstractPokitService()
43 7352 : {
44 13922 : delete d_ptr;
45 13922 : }
46 :
47 : /*!
48 : * \fn virtual bool AbstractPokitService::readCharacteristics() = 0
49 : *
50 : * Read all characteristics.
51 : *
52 : * This convenience function will queue refresh requests of all characteristics supported by this
53 : * service.
54 : *
55 : * Relevant `*Service::*Read` signals will be emitted by derived class objects as each
56 : * characteristic is successfully read.
57 : */
58 :
59 : /*!
60 : * Returns `true` if autodiscovery of services and service details is enabled, `false` otherwise.
61 : *
62 : * \see setAutoDiscover for more information on what autodiscovery provides.
63 : */
64 135 : bool AbstractPokitService::autoDiscover() const
65 156 : {
66 156 : Q_D(const AbstractPokitService);
67 291 : return d->autoDiscover;
68 156 : }
69 :
70 : /*!
71 : * If \a discover is \c true, autodiscovery will be attempted.
72 : *
73 : * Specifically, this may resulting in automatic invocation of:
74 : * * QLowEnergyController::discoverServices if/when the internal controller is connected; and
75 : * * QLowEnergyService::discoverDetails if/when an internal service object is created.
76 : *
77 : * \see autoDiscover
78 : */
79 90 : void AbstractPokitService::setAutoDiscover(const bool discover)
80 104 : {
81 104 : Q_D(AbstractPokitService);
82 194 : d->autoDiscover = discover;
83 194 : }
84 :
85 : /*!
86 : * Returns the Pokit product this service is attached to.
87 : *
88 : * \see setPokitProduct
89 : */
90 12825 : std::optional<PokitProduct> AbstractPokitService::pokitProduct() const
91 7710 : {
92 7710 : Q_D(const AbstractPokitService);
93 20535 : return d->pokitProduct;
94 7710 : }
95 :
96 : /*!
97 : * Sets the current Pokit \a product.
98 : *
99 : * This must be called to set the product before this object's BLE controller's services are discovered. If
100 : * autoDiscover() is enabled, then this should be done before the controller's \c connectToDevice() is called.
101 : *
102 : * For example:
103 : * ```
104 : * Q_ASSERT(isPokitProduct(deviceInfo));
105 : * auto controller = QLowEnergyController::createCentral(deviceInfo);
106 : * auto service = new DsoService(controller);
107 : * service->setPokitProduct(pokitProduct(deviceInfo));
108 : * controller->connectToDevice();
109 : * ```
110 : *
111 : * \see autoDiscover
112 : * \see pokitProduct
113 : */
114 11205 : void AbstractPokitService::setPokitProduct(const PokitProduct product)
115 6798 : {
116 6798 : Q_D(AbstractPokitService);
117 18003 : d->pokitProduct = product;
118 18003 : }
119 :
120 : /*!
121 : * Returns a non-const pointer to the internal service object, if any.
122 : */
123 405 : QLowEnergyService * AbstractPokitService::service()
124 318 : {
125 318 : Q_D(AbstractPokitService);
126 723 : return d->service;
127 318 : }
128 :
129 : /*!
130 : * Returns a const pointer to the internal service object, if any.
131 : */
132 45 : const QLowEnergyService * AbstractPokitService::service() const
133 52 : {
134 52 : Q_D(const AbstractPokitService);
135 97 : return d->service;
136 52 : }
137 :
138 : /*!
139 : * \fn void AbstractPokitService::serviceDetailsDiscovered()
140 : *
141 : * This signal is emitted when the Pokit service details have been discovered.
142 : *
143 : * Once this signal has been emitted, cached characteristics values should be immediately available
144 : * via derived classes' accessor functions, and refreshes can be queued via readCharacteristics()
145 : * and any related read functions provided by derived classes.
146 : */
147 :
148 : /*!
149 : * \fn void AbstractPokitService::serviceErrorOccurred(QLowEnergyService::ServiceError newError)
150 : *
151 : * This signal is emitted whenever an error occurs on the underlying QLowEnergyService.
152 : */
153 :
154 : /*!
155 : * \cond internal
156 : * \class AbstractPokitServicePrivate
157 : *
158 : * The AbstractPokitServicePrivate class provides private implementation for AbstractPokitService.
159 : */
160 :
161 : /*!
162 : * \internal
163 : * Constructs a new AbstractPokitServicePrivate object with public implementation \a q.
164 : *
165 : * Note, typically the \a serviceUuid should be set validly, however, in the rare case that a
166 : * service's UUID can vary (ie the Status Service), \a serviceUuid may be set to a `null`
167 : * QBluetoothUuid here, and updated when the correct service UUID is known.
168 : *
169 : * \see StatusService::ServiceUuids
170 : * \see StatusServicePrivate::serviceDiscovered
171 : */
172 17595 : AbstractPokitServicePrivate::AbstractPokitServicePrivate(const QBluetoothUuid &serviceUuid,
173 17595 : QLowEnergyController * controller, AbstractPokitService * const q)
174 28357 : : controller(controller), serviceUuid(serviceUuid), q_ptr(q)
175 12922 : {
176 30517 : if (controller) {
177 16281 : connect(controller, &QLowEnergyController::connected,
178 8019 : this, &AbstractPokitServicePrivate::connected);
179 :
180 16281 : connect(controller, &QLowEnergyController::discoveryFinished,
181 8019 : this, &AbstractPokitServicePrivate::discoveryFinished);
182 :
183 16281 : connect(controller, &QLowEnergyController::serviceDiscovered,
184 8019 : this, &AbstractPokitServicePrivate::serviceDiscovered);
185 :
186 16281 : createServiceObject();
187 5346 : }
188 30517 : }
189 :
190 : /*!
191 : * Creates an internal service object from the internal controller.
192 : *
193 : * Any existing service object will *not* be replaced.
194 : *
195 : * Returns \c true if a service was created successfully, either now, or sometime previously.
196 : */
197 11295 : bool AbstractPokitServicePrivate::createServiceObject()
198 5612 : {
199 16907 : if (!controller) {
200 178 : return false;
201 178 : }
202 :
203 16549 : if (service) {
204 80 : qCDebug(lc).noquote() << tr("Already have service object:") << service;
205 54 : return true;
206 22 : }
207 :
208 15744 : if (serviceUuid.isNull()) {
209 800 : qCDebug(lc).noquote() << tr("Service UUID not assigned yet.");
210 540 : return false;
211 220 : }
212 :
213 15812 : service = controller->createServiceObject(serviceUuid, this);
214 15812 : if (!service) {
215 5192 : return false;
216 5192 : }
217 0 : qCDebug(lc).noquote() << tr("Service object created for %1 device:").arg(toString(*this->pokitProduct)) << service;
218 :
219 0 : connect(service, &QLowEnergyService::stateChanged,
220 0 : this, &AbstractPokitServicePrivate::stateChanged);
221 0 : connect(service, &QLowEnergyService::characteristicRead,
222 0 : this, &AbstractPokitServicePrivate::characteristicRead);
223 0 : connect(service, &QLowEnergyService::characteristicWritten,
224 0 : this, &AbstractPokitServicePrivate::characteristicWritten);
225 0 : connect(service, &QLowEnergyService::characteristicChanged,
226 0 : this, &AbstractPokitServicePrivate::characteristicChanged);
227 :
228 0 : connect(service, &QLowEnergyService::descriptorRead, this,
229 0 : [](const QLowEnergyDescriptor &descriptor, const QByteArray &value){
230 0 : qCDebug(lc).noquote() << tr(R"(Descriptor "%1" (%2) read.)")
231 0 : .arg(descriptor.name(), descriptor.uuid().toString());
232 0 : Q_UNUSED(value)
233 0 : });
234 :
235 0 : connect(service, &QLowEnergyService::descriptorWritten, this,
236 0 : [](const QLowEnergyDescriptor &descriptor, const QByteArray &newValue){
237 0 : qCDebug(lc).noquote() << tr(R"(Descriptor "%1" (%2) written.)")
238 0 : .arg(descriptor.name(), descriptor.uuid().toString());
239 0 : Q_UNUSED(newValue)
240 0 : });
241 :
242 0 : connect(service,
243 0 : #if (QT_VERSION < QT_VERSION_CHECK(6, 2, 0))
244 0 : QOverload<QLowEnergyService::ServiceError>::of(&QLowEnergyService::error),
245 : #else
246 0 : &QLowEnergyService::errorOccurred,
247 0 : #endif
248 0 : this, &AbstractPokitServicePrivate::errorOccurred);
249 :
250 0 : if (autoDiscover) {
251 0 : service->discoverDetails();
252 0 : }
253 0 : return true;
254 5192 : }
255 :
256 : /*!
257 : * Get \a uuid characteristic from the underlying service. This helper function is equivalent to
258 : *
259 : * ```
260 : * return service->characteristic(uuid);
261 : * ```
262 : *
263 : * except that it performs some sanity checks, such as checking the service object pointer has been
264 : * assigned first, and also logs failures in a consistent manner.
265 : *
266 : * \param uuid
267 : * \return
268 : */
269 11655 : QLowEnergyCharacteristic AbstractPokitServicePrivate::getCharacteristic(const QBluetoothUuid &uuid) const
270 7978 : {
271 19633 : if (!service) {
272 23000 : qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" requested before service assigned.)")
273 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
274 19633 : return QLowEnergyCharacteristic();
275 7978 : }
276 :
277 0 : if (const QLowEnergyCharacteristic characteristic = service->characteristic(uuid); characteristic.isValid()) {
278 0 : return characteristic;
279 0 : }
280 :
281 0 : if (service->state() != QLowEnergyService::
282 0 : #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
283 0 : ServiceDiscovered
284 : #else
285 0 : RemoteServiceDiscovered
286 0 : #endif
287 0 : ) {
288 0 : qCWarning(lc).noquote() << tr(R"(Characteristic %1 "%2" requested before service %3 "%4" discovered.)")
289 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid),
290 0 : service->serviceUuid().toString(), PokitDevice::serviceToString(service->serviceUuid()));
291 0 : qCInfo(lc).noquote() << tr("Current service state:") << service->state();
292 0 : return QLowEnergyCharacteristic();
293 0 : }
294 :
295 0 : qCWarning(lc).noquote() << tr(R"(Characteristic %1 "%2" not found in service %3 "%4".)")
296 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid),
297 0 : service->serviceUuid().toString(), PokitDevice::serviceToString(service->serviceUuid()));
298 0 : return QLowEnergyCharacteristic();
299 0 : }
300 :
301 : /*!
302 : * Read the \a uuid characteristic.
303 : *
304 : * If successful, the `QLowEnergyService::characteristicRead` signal will be emitted by the internal
305 : * service object. For convenience, derived classes should implement the characteristicRead()
306 : * virtual function to handle the read value.
307 : *
308 : * Returns \c true if the characteristic read request was successfully queued, \c false otherwise.
309 : *
310 : * \see AbstractPokitService::readCharacteristics()
311 : * \see AbstractPokitServicePrivate::characteristicRead()
312 : */
313 1170 : bool AbstractPokitServicePrivate::readCharacteristic(const QBluetoothUuid &uuid)
314 1352 : {
315 2522 : const QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
316 2522 : if (!characteristic.isValid()) {
317 1352 : return false;
318 1352 : }
319 0 : qCDebug(lc).noquote() << tr(R"(Reading characteristic %1 "%2".)")
320 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
321 0 : service->readCharacteristic(characteristic);
322 0 : return true;
323 2522 : }
324 :
325 : /*!
326 : * Enables client (Pokit device) side notification for characteristic \a uuid.
327 : *
328 : * Returns \c true if the notification enable request was successfully queued, \c false otherwise.
329 : *
330 : * \see AbstractPokitServicePrivate::characteristicChanged
331 : * \see AbstractPokitServicePrivate::disableCharacteristicNotificatons
332 : */
333 405 : bool AbstractPokitServicePrivate::enableCharacteristicNotificatons(const QBluetoothUuid &uuid)
334 468 : {
335 990 : qCDebug(lc).noquote() << tr(R"(Enabling CCCD for characteristic %1 "%2".)")
336 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
337 873 : QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
338 873 : if (!characteristic.isValid()) {
339 468 : return false;
340 468 : }
341 :
342 0 : QLowEnergyDescriptor descriptor = characteristic.descriptor(
343 0 : QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration); // 0x2902
344 0 : if (!descriptor.isValid()) {
345 0 : qCWarning(lc).noquote() << tr(R"(Characteristic %1 "%2" has no client configuration descriptor.)")
346 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
347 0 : return false;
348 0 : }
349 :
350 0 : service->writeDescriptor(descriptor,
351 0 : #if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 0))
352 0 : QLowEnergyCharacteristic::CCCDEnableNotification
353 : #else
354 0 : QByteArray::fromHex("0100") // See Qt6's QLowEnergyCharacteristic::CCCDEnableNotification.
355 0 : #endif
356 0 : );
357 0 : return true;
358 405 : }
359 :
360 : /*!
361 : * Disables client (Pokit device) side notification for characteristic \a uuid.
362 : *
363 : * Returns \c true if the notification disable request was successfully queued, \c false otherwise.
364 : *
365 : * \see AbstractPokitServicePrivate::characteristicChanged
366 : * \see AbstractPokitServicePrivate::enableCharacteristicNotificatons
367 : */
368 405 : bool AbstractPokitServicePrivate::disableCharacteristicNotificatons(const QBluetoothUuid &uuid)
369 468 : {
370 990 : qCDebug(lc).noquote() << tr(R"(Disabling CCCD for characteristic %1 "%2".)")
371 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
372 873 : QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
373 873 : if (!characteristic.isValid()) {
374 468 : return false;
375 468 : }
376 :
377 0 : QLowEnergyDescriptor descriptor = characteristic.descriptor(
378 0 : QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration); // 0x2902
379 0 : if (!descriptor.isValid()) {
380 0 : qCWarning(lc).noquote() << tr(R"(Characteristic %1 "%2" has no client configuration descriptor.)")
381 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
382 0 : return false;
383 0 : }
384 :
385 0 : service->writeDescriptor(descriptor,
386 0 : #if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 0))
387 0 : QLowEnergyCharacteristic::CCCDDisable
388 : #else
389 0 : QByteArray::fromHex("0000") // See Qt6's QLowEnergyCharacteristic::CCCDDisable.
390 0 : #endif
391 0 : );
392 0 : return true;
393 405 : }
394 :
395 : /*!
396 : * Returns `false` if \a data is smaller than \a minSize, otherwise returns \a failOnMax if \a data
397 : * is bigger than \a maxSize, otherwise returns `true`.
398 : *
399 : * A warning is logged if either \a minSize or \a maxSize is violated, regardless of the returned
400 : * value; ie this funcion can be used to simply warn if \a data is too big, or it can be used to
401 : * failed (return `false`) in that case.
402 : */
403 1890 : bool AbstractPokitServicePrivate::checkSize(const QString &label, const QByteArray &data,
404 : const int minSize, const int maxSize,
405 : const bool failOnMax)
406 2184 : {
407 4074 : if (data.size() < minSize) {
408 2015 : qCWarning(lc).noquote() << tr("%1 requires %n byte/s, but only %2 present: %3", nullptr, minSize)
409 1924 : .arg(label).arg(data.size()).arg(toHexString(data));
410 1092 : return false;
411 676 : }
412 2813 : if ((maxSize >= 0) && (data.size() > maxSize)) {
413 895 : qCWarning(lc).noquote() << tr("%1 has %n extraneous byte/s: %2", nullptr, data.size()-maxSize)
414 660 : .arg(label, toHexString(data.mid(maxSize)));
415 485 : return (!failOnMax);
416 260 : }
417 1248 : return true;
418 1508 : }
419 :
420 : /*!
421 : * Returns up to \a maxSize bytes of \a data as a human readable hexadecimal string. If \a data
422 : * exceeds \a maxSize, then \a data is elided in the middle. For example:
423 : *
424 : * ```
425 : * toHex(QBytArray("\x1\x2\x3\x4\x5\x6", 4); // "0x01,02,...,05,06"
426 : * ```
427 : */
428 1170 : QString AbstractPokitServicePrivate::toHexString(const QByteArray &data, const int maxSize)
429 1352 : {
430 1898 : return (data.size() <= maxSize)
431 4751 : ? QString::fromLatin1("0x%1").arg(QLatin1String(data.toHex(',')))
432 1577 : : QString::fromLatin1("0x%1,...,%2").arg(
433 1524 : QLatin1String(data.left(maxSize/2-1).toHex(',')),
434 4763 : QLatin1String(data.right(maxSize/2-1).toHex(',')));
435 1352 : }
436 :
437 : /*!
438 : * Handles `QLowEnergyController::connected` events.
439 : *
440 : * If `autoDiscover` is enabled, this will begin service discovery on the newly connected controller.
441 : *
442 : * \see AbstractPokitService::autoDiscover()
443 : */
444 90 : void AbstractPokitServicePrivate::connected()
445 44 : {
446 134 : if (!controller) {
447 146 : qCWarning(lc).noquote() << tr("Connected with no controller set") << sender();
448 54 : return;
449 22 : }
450 :
451 80 : qCDebug(lc).noquote() << tr(R"(Connected to "%1" (%2) at %3.)").arg(
452 0 : controller->remoteName(), controller->remoteDeviceUuid().toString(),
453 0 : controller->remoteAddress().toString());
454 67 : if (autoDiscover) {
455 67 : controller->discoverServices();
456 22 : }
457 22 : }
458 :
459 : /*!
460 : * Handles `QLowEnergyController::discoveryFinished` events.
461 : *
462 : * As this event indicates that the controller has finished discovering services, this function will
463 : * invoke createServiceObject() to create the internal service object (if not already created).
464 : */
465 90 : void AbstractPokitServicePrivate::discoveryFinished()
466 44 : {
467 134 : if (!controller) {
468 146 : qCWarning(lc).noquote() << tr("Discovery finished with no controller set") << sender();
469 54 : return;
470 22 : }
471 :
472 80 : qCDebug(lc).noquote() << tr(R"(Discovery finished for "%1" (%2) at %3.)").arg(
473 0 : controller->remoteName(), controller->remoteDeviceUuid().toString(),
474 0 : controller->remoteAddress().toString());
475 :
476 67 : if (!createServiceObject()) {
477 146 : qCWarning(lc).noquote() << tr("Discovery finished, but service not found.");
478 22 : Q_Q(AbstractPokitService);
479 67 : Q_EMIT q->serviceErrorOccurred(QLowEnergyService::ServiceError::UnknownError);
480 22 : }
481 22 : }
482 :
483 : /*!
484 : * Handles `QLowEnergyController::errorOccurred` events.
485 : *
486 : * This function simply re-emits \a newError as AbstractPokitService::serviceErrorOccurred.
487 : */
488 45 : void AbstractPokitServicePrivate::errorOccurred(const QLowEnergyService::ServiceError newError)
489 52 : {
490 52 : Q_Q(AbstractPokitService);
491 110 : qCDebug(lc).noquote() << tr("Service error") << newError;
492 97 : Q_EMIT q->serviceErrorOccurred(newError);
493 97 : }
494 :
495 : /*!
496 : * Handles `QLowEnergyController::serviceDiscovered` events.
497 : *
498 : * If the discovered service is the one this (or rather the derived) class wraps, then
499 : * createServiceObject() will be invoked immediately (otherwise it will be invoked after full
500 : * service discovery has completed, ie in discoveryFinished()).
501 : */
502 270 : void AbstractPokitServicePrivate::serviceDiscovered(const QBluetoothUuid &newService)
503 312 : {
504 582 : if ((!service) && (newService == serviceUuid)) {
505 330 : qCDebug(lc).noquote() << tr("Service discovered") << newService;
506 291 : createServiceObject();
507 156 : }
508 582 : }
509 :
510 : /*!
511 : * Handles `QLowEnergyController::stateChanged` events.
512 : *
513 : * If \a newState indicates that service details have now been discovered, then
514 : * AbstractPokitService::serviceDetailsDiscovered will be emitted.
515 : *
516 : * \see AbstractPokitService::autoDiscover()
517 : */
518 135 : void AbstractPokitServicePrivate::stateChanged(QLowEnergyService::ServiceState newState)
519 156 : {
520 330 : qCDebug(lc).noquote() << tr("State changed to") << newState;
521 :
522 291 : if (lc().isDebugEnabled()) {
523 0 : for (const auto &characteristic: service->characteristics()) {
524 0 : QStringList properties;
525 : /// \cond no-doxygen
526 0 : #define QTPOKIT_INTERNAL_TEST_AND_APPEND(property) \
527 0 : if (characteristic.properties().testFlag(QLowEnergyCharacteristic::property)) { \
528 0 : properties.append(QStringLiteral(#property).toLower());\
529 0 : }
530 : /// \endcond
531 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(Broadcasting)
532 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(Read)
533 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(WriteNoResponse)
534 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(Write)
535 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(Notify)
536 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(Indicate)
537 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(WriteSigned)
538 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(ExtendedProperty)
539 0 : #undef QTPOKIT_INTERNAL_TEST_AND_APPEND
540 0 : qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" supports %3.)").arg(characteristic.uuid().toString(),
541 0 : PokitDevice::charcteristicToString(characteristic.uuid()), properties.join(u", "_s));
542 0 : }
543 0 : }
544 :
545 291 : if (newState == QLowEnergyService::
546 69 : #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
547 69 : ServiceDiscovered
548 : #else
549 87 : RemoteServiceDiscovered
550 87 : #endif
551 156 : ) {
552 52 : Q_Q(AbstractPokitService);
553 110 : qCDebug(lc).noquote() << tr("Service details discovered.");
554 97 : Q_EMIT q->serviceDetailsDiscovered();
555 52 : }
556 291 : }
557 :
558 : /*!
559 : * Handles `QLowEnergyService::characteristicRead` events. This base implementation simply debug
560 : * logs the event.
561 : *
562 : * Derived classes should implement this function to handle the successful reads of
563 : * \a characteristic, typically by parsing \a value, then emitting a specialised signal.
564 : */
565 270 : void AbstractPokitServicePrivate::characteristicRead(
566 : const QLowEnergyCharacteristic &characteristic, const QByteArray &value)
567 312 : {
568 660 : qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" read %n byte/s: %3)", nullptr, value.size()).arg(
569 0 : characteristic.uuid().toString(), PokitDevice::charcteristicToString(characteristic.uuid()), toHexString(value));
570 582 : }
571 :
572 : /*!
573 : * Handles `QLowEnergyService::characteristicWritten` events. This base implementation simply debug
574 : * logs the event.
575 : *
576 : * Derived classes should implement this function to handle the successful writes of
577 : * \a characteristic, typically by parsing \a newValue, then emitting a specialised signal.
578 : */
579 270 : void AbstractPokitServicePrivate::characteristicWritten(
580 : const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
581 312 : {
582 660 : qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" written with %Ln byte/s: %3)", nullptr, newValue.size())
583 0 : .arg(characteristic.uuid().toString(), PokitDevice::charcteristicToString(characteristic.uuid()), toHexString(newValue));
584 582 : }
585 :
586 : /*!
587 : * Handles `QLowEnergyService::characteristicChanged` events. This base implementation simply debug
588 : * logs the event.
589 : *
590 : * If derived classes support characteristics with client-side notification (ie Notify, as opposed
591 : * to Read or Write operations), they should implement this function to handle the successful reads of
592 : * \a characteristic, typically by parsing \a value, then emitting a specialised signal.
593 : */
594 180 : void AbstractPokitServicePrivate::characteristicChanged(
595 : const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
596 208 : {
597 440 : qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" changed to %Ln byte/s: %3)", nullptr, newValue.size())
598 0 : .arg(characteristic.uuid().toString(), PokitDevice::charcteristicToString(characteristic.uuid()), toHexString(newValue));
599 388 : }
600 :
601 : /// \endcond
602 :
603 : QTPOKIT_END_NAMESPACE
|