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