Line data Source code
1 : // SPDX-FileCopyrightText: 2022-2023 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 7098 : AbstractPokitService::AbstractPokitService(
28 7098 : AbstractPokitServicePrivate * const d, QObject * const parent)
29 7098 : : QObject(parent), d_ptr(d)
30 : {
31 :
32 7098 : }
33 : /// \endcond
34 :
35 : /*!
36 : * Destroys this AbstractPokitService object.
37 : */
38 2790 : AbstractPokitService::~AbstractPokitService()
39 : {
40 2790 : delete d_ptr;
41 2790 : }
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 54 : bool AbstractPokitService::autoDiscover() const
61 : {
62 : Q_D(const AbstractPokitService);
63 54 : return d->autoDiscover;
64 : }
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 36 : void AbstractPokitService::setAutoDiscover(const bool discover)
76 : {
77 : Q_D(AbstractPokitService);
78 36 : d->autoDiscover = discover;
79 36 : }
80 :
81 : /*!
82 : * Returns the Pokit product this service is attached to.
83 : *
84 : * \see setPokitProduct
85 : */
86 5130 : std::optional<PokitProduct> AbstractPokitService::pokitProduct() const
87 : {
88 : Q_D(const AbstractPokitService);
89 5130 : return d->pokitProduct;
90 : }
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 4482 : void AbstractPokitService::setPokitProduct(const PokitProduct product)
111 : {
112 : Q_D(AbstractPokitService);
113 4482 : d->pokitProduct = product;
114 4482 : }
115 :
116 : /*!
117 : * Returns a non-const pointer to the internal service object, if any.
118 : */
119 162 : QLowEnergyService * AbstractPokitService::service()
120 : {
121 : Q_D(AbstractPokitService);
122 162 : return d->service;
123 : }
124 :
125 : /*!
126 : * Returns a const pointer to the internal service object, if any.
127 : */
128 18 : const QLowEnergyService * AbstractPokitService::service() const
129 : {
130 : Q_D(const AbstractPokitService);
131 18 : return d->service;
132 : }
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 7098 : AbstractPokitServicePrivate::AbstractPokitServicePrivate(const QBluetoothUuid &serviceUuid,
169 7098 : QLowEnergyController * controller, AbstractPokitService * const q)
170 7098 : : controller(controller), serviceUuid(serviceUuid), q_ptr(q)
171 : {
172 7098 : if (controller) {
173 4254 : connect(controller, &QLowEnergyController::connected,
174 : this, &AbstractPokitServicePrivate::connected);
175 :
176 4254 : connect(controller, &QLowEnergyController::discoveryFinished,
177 : this, &AbstractPokitServicePrivate::discoveryFinished);
178 :
179 4254 : connect(controller, &QLowEnergyController::serviceDiscovered,
180 : this, &AbstractPokitServicePrivate::serviceDiscovered);
181 :
182 4254 : createServiceObject();
183 : }
184 7098 : }
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 4398 : bool AbstractPokitServicePrivate::createServiceObject()
194 : {
195 4398 : if (!controller) {
196 : return false;
197 : }
198 :
199 4326 : if (service) {
200 18 : qCDebug(lc).noquote() << tr("Already have service object:") << service;
201 18 : return true;
202 : }
203 :
204 4308 : if (serviceUuid.isNull()) {
205 180 : qCDebug(lc).noquote() << tr("Service UUID not assigned yet.");
206 180 : return false;
207 : }
208 :
209 4128 : service = controller->createServiceObject(serviceUuid, this);
210 4128 : if (!service) {
211 : return false;
212 : }
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 : this, &AbstractPokitServicePrivate::stateChanged);
217 0 : connect(service, &QLowEnergyService::characteristicRead,
218 : this, &AbstractPokitServicePrivate::characteristicRead);
219 0 : connect(service, &QLowEnergyService::characteristicWritten,
220 : this, &AbstractPokitServicePrivate::characteristicWritten);
221 0 : connect(service, &QLowEnergyService::characteristicChanged,
222 : this, &AbstractPokitServicePrivate::characteristicChanged);
223 :
224 0 : connect(service, &QLowEnergyService::descriptorRead,
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 : Q_UNUSED(value)
229 0 : });
230 :
231 0 : connect(service, &QLowEnergyService::descriptorWritten,
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 : Q_UNUSED(newValue)
236 0 : });
237 :
238 0 : connect(service,
239 : #if (QT_VERSION < QT_VERSION_CHECK(6, 2, 0))
240 : QOverload<QLowEnergyService::ServiceError>::of(&QLowEnergyService::error),
241 : #else
242 : &QLowEnergyService::errorOccurred,
243 : #endif
244 : this, &AbstractPokitServicePrivate::errorOccurred);
245 :
246 0 : if (autoDiscover) {
247 0 : service->discoverDetails();
248 : }
249 : return true;
250 : }
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 4068 : QLowEnergyCharacteristic AbstractPokitServicePrivate::getCharacteristic(const QBluetoothUuid &uuid) const
266 : {
267 4068 : if (!service) {
268 4068 : qCDebug(lc).noquote() << tr(R"(Characterisitc %1 "%2" requested before service assigned.)")
269 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
270 4068 : return QLowEnergyCharacteristic();
271 : }
272 :
273 0 : const QLowEnergyCharacteristic characteristic = service->characteristic(uuid);
274 0 : if (characteristic.isValid()) {
275 0 : return characteristic;
276 : }
277 :
278 0 : if (service->state() != QLowEnergyService::
279 : #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
280 : ServiceDiscovered
281 : #else
282 : RemoteServiceDiscovered
283 : #endif
284 : ) {
285 0 : qCWarning(lc).noquote() << tr(R"(Characterisitc %1 "%2" requested before service %3 "%4" discovered.)")
286 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid),
287 0 : service->serviceUuid().toString(), PokitDevice::serviceToString(service->serviceUuid()));
288 0 : qCInfo(lc).noquote() << tr("Current service state:") << service->state();
289 0 : return QLowEnergyCharacteristic();
290 : }
291 :
292 0 : qCWarning(lc).noquote() << tr(R"(Characterisitc %1 "%2" not found in service %3 "%4".)")
293 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid),
294 0 : service->serviceUuid().toString(), PokitDevice::serviceToString(service->serviceUuid()));
295 0 : return QLowEnergyCharacteristic();
296 0 : }
297 :
298 : /*!
299 : * Read the \a uuid characteristic.
300 : *
301 : * If succesful, the `QLowEnergyService::characteristicRead` signal will be emitted by the internal
302 : * service object. For convenience, derived classes should implement the characteristicRead()
303 : * virtual function to handle the read value.
304 : *
305 : * Returns \c true if the characteristic read request was successfully queued, \c false otherwise.
306 : *
307 : * \see AbstractPokitService::readCharacteristics()
308 : * \see AbstractPokitServicePrivate::characteristicRead()
309 : */
310 540 : bool AbstractPokitServicePrivate::readCharacteristic(const QBluetoothUuid &uuid)
311 : {
312 540 : const QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
313 540 : if (!characteristic.isValid()) {
314 : return false;
315 : }
316 0 : qCDebug(lc).noquote() << tr(R"(Reading characteristic %1 "%2".)")
317 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
318 0 : service->readCharacteristic(characteristic);
319 : return true;
320 540 : }
321 :
322 : /*!
323 : * Enables client (Pokit device) side notification for characteristic \a uuid.
324 : *
325 : * Returns \c true if the notication enable request was successfully queued, \c false otherwise.
326 : *
327 : * \see AbstractPokitServicePrivate::characteristicChanged
328 : * \see AbstractPokitServicePrivate::disableCharacteristicNotificatons
329 : */
330 162 : bool AbstractPokitServicePrivate::enableCharacteristicNotificatons(const QBluetoothUuid &uuid)
331 : {
332 162 : qCDebug(lc).noquote() << tr(R"(Enabling CCCD for characteristic %1 "%2".)")
333 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
334 162 : QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
335 162 : if (!characteristic.isValid()) {
336 : return false;
337 : }
338 :
339 : QLowEnergyDescriptor descriptor = characteristic.descriptor(
340 0 : QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);
341 0 : if (!descriptor.isValid()) {
342 0 : qCWarning(lc).noquote() << tr(R"(Characterisitc %1 "%2" has no client configuration descriptor.)")
343 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
344 0 : return false;
345 : }
346 :
347 0 : service->writeDescriptor(descriptor,
348 : #if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 0))
349 : QLowEnergyCharacteristic::CCCDEnableNotification
350 : #else
351 0 : QByteArray::fromHex("0100") // See Qt6's QLowEnergyCharacteristic::CCCDEnableNotification.
352 : #endif
353 : );
354 0 : return true;
355 162 : }
356 :
357 : /*!
358 : * Disables client (Pokit device) side notification for characteristic \a uuid.
359 : *
360 : * Returns \c true if the notication disable request was successfully queued, \c false otherwise.
361 : *
362 : * \see AbstractPokitServicePrivate::characteristicChanged
363 : * \see AbstractPokitServicePrivate::enableCharacteristicNotificatons
364 : */
365 162 : bool AbstractPokitServicePrivate::disableCharacteristicNotificatons(const QBluetoothUuid &uuid)
366 : {
367 162 : qCDebug(lc).noquote() << tr(R"(Disabling CCCD for characteristic %1 "%2".)")
368 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
369 162 : QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
370 162 : if (!characteristic.isValid()) {
371 : return false;
372 : }
373 :
374 : QLowEnergyDescriptor descriptor = characteristic.descriptor(
375 0 : QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);
376 0 : if (!descriptor.isValid()) {
377 0 : qCWarning(lc).noquote() << tr(R"(Characterisitc %1 "%2" has no client configuration descriptor.)")
378 0 : .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
379 0 : return false;
380 : }
381 :
382 0 : service->writeDescriptor(descriptor,
383 : #if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 0))
384 : QLowEnergyCharacteristic::CCCDDisable
385 : #else
386 0 : QByteArray::fromHex("0000") // See Qt6's QLowEnergyCharacteristic::CCCDDisable.
387 : #endif
388 : );
389 0 : return true;
390 162 : }
391 :
392 : /*!
393 : * Returns `false` if \a data is smaller than \a minSize, otherwise returns \a failOnMax if \a data
394 : * is bigger than \a maxSize, otherwise returns `true`.
395 : *
396 : * A warning is logged if either \a minSize or \a maxSize is violated, regardless of the returned
397 : * value; ie this funcion can be used to simply warn if \a data is too big, or it can be used to
398 : * failed (return `false`) in that case.
399 : */
400 900 : bool AbstractPokitServicePrivate::checkSize(const QString &label, const QByteArray &data,
401 : const int minSize, const int maxSize,
402 : const bool failOnMax)
403 : {
404 900 : if (data.size() < minSize) {
405 540 : qCWarning(lc).noquote() << tr("%1 requires %n byte/s, but only %2 present: %3", nullptr, minSize)
406 540 : .arg(label).arg(data.size()).arg(toHexString(data));
407 270 : return false;
408 : }
409 630 : if ((maxSize >= 0) && (data.size() > maxSize)) {
410 800 : qCWarning(lc).noquote() << tr("%1 has %n extraneous byte/s: %2", nullptr, data.size()-maxSize)
411 496 : .arg(label, toHexString(data.mid(maxSize)));
412 288 : return (!failOnMax);
413 : }
414 : return true;
415 : }
416 :
417 : /*!
418 : * Returns up to \a maxSize bytes of \a data as a human readable hexadecimal string. If \a data
419 : * exceeds \a maxSize, then \a data is elided in the middle. For example:
420 : *
421 : * ```
422 : * toHex(QBytArray("\x1\x2\x3\x4\x5\x6", 4); // "0x01,02,...,05,06"
423 : * ```
424 : */
425 702 : QString AbstractPokitServicePrivate::toHexString(const QByteArray &data, const int maxSize)
426 : {
427 156 : return (data.size() <= maxSize)
428 1890 : ? QString::fromLatin1("0x%1").arg(QLatin1String(data.toHex(',')))
429 54 : : QString::fromLatin1("0x%1,...,%2").arg(
430 768 : QLatin1String(data.left(maxSize/2-1).toHex(',')),
431 3300 : QLatin1String(data.right(maxSize/2-1).toHex(',')));
432 : }
433 :
434 : /*!
435 : * Handles `QLowEnergyController::connected` events.
436 : *
437 : * If `autoDiscover` is enabled, this will begin service discovery on the newly connected contoller.
438 : *
439 : * \see AbstractPokitService::autoDiscover()
440 : */
441 36 : void AbstractPokitServicePrivate::connected()
442 : {
443 36 : if (!controller) {
444 36 : qCWarning(lc).noquote() << tr("Connected with no controller set") << sender();
445 18 : return;
446 : }
447 :
448 18 : qCDebug(lc).noquote() << tr(R"(Connected to "%1" (%2) at %3.)").arg(
449 0 : controller->remoteName(), controller->remoteDeviceUuid().toString(),
450 0 : controller->remoteAddress().toString());
451 18 : if (autoDiscover) {
452 18 : controller->discoverServices();
453 : }
454 : }
455 :
456 : /*!
457 : * Handles `QLowEnergyController::discoveryFinished` events.
458 : *
459 : * As this event indicates that the conroller has finished discovering services, this function will
460 : * invoke createServiceObject() to create the internal service object (if not already created).
461 : */
462 36 : void AbstractPokitServicePrivate::discoveryFinished()
463 : {
464 36 : if (!controller) {
465 36 : qCWarning(lc).noquote() << tr("Discovery finished with no controller set") << sender();
466 18 : return;
467 : }
468 :
469 18 : qCDebug(lc).noquote() << tr(R"(Discovery finished for "%1" (%2) at %3.)").arg(
470 0 : controller->remoteName(), controller->remoteDeviceUuid().toString(),
471 0 : controller->remoteAddress().toString());
472 :
473 18 : if (!createServiceObject()) {
474 40 : qCWarning(lc).noquote() << tr("Discovery finished, but service not found.");
475 : Q_Q(AbstractPokitService);
476 18 : emit q->serviceErrorOccurred(QLowEnergyService::ServiceError::UnknownError);
477 : }
478 : }
479 :
480 : /*!
481 : * Handles `QLowEnergyController::errorOccurred` events.
482 : *
483 : * This function simply re-emits \a newError as AbstractPokitService::serviceErrorOccurred.
484 : */
485 18 : void AbstractPokitServicePrivate::errorOccurred(const QLowEnergyService::ServiceError newError)
486 : {
487 : Q_Q(AbstractPokitService);
488 18 : qCDebug(lc).noquote() << tr("Service error") << newError;
489 18 : emit q->serviceErrorOccurred(newError);
490 18 : }
491 :
492 : /*!
493 : * Handles `QLowEnergyController::serviceDiscovered` events.
494 : *
495 : * If the discovered service is the one this (or rather the derived) class wraps, then
496 : * createServiceObject() will be invoked immediately (otherwise it will be invoked after full
497 : * service discovery has completed, ie in discoveryFinished()).
498 : */
499 108 : void AbstractPokitServicePrivate::serviceDiscovered(const QBluetoothUuid &newService)
500 : {
501 108 : if ((!service) && (newService == serviceUuid)) {
502 54 : qCDebug(lc).noquote() << tr("Service discovered") << newService;
503 54 : createServiceObject();
504 : }
505 108 : }
506 :
507 : /*!
508 : * Handles `QLowEnergyController::stateChanged` events.
509 : *
510 : * If \a newState indicates that service details have now been discovered, then
511 : * AbstractPokitService::serviceDetailsDiscovered will be emitted.
512 : *
513 : * \see AbstractPokitService::autoDiscover()
514 : */
515 54 : void AbstractPokitServicePrivate::stateChanged(QLowEnergyService::ServiceState newState)
516 : {
517 54 : qCDebug(lc).noquote() << tr("State changed to") << newState;
518 :
519 54 : if (lc().isDebugEnabled()) {
520 0 : for (const auto &characteristic: service->characteristics()) {
521 0 : QStringList properties;
522 : /// \cond no-doxygen
523 : #define QTPOKIT_INTERNAL_TEST_AND_APPEND(property) \
524 : if (characteristic.properties().testFlag(QLowEnergyCharacteristic::property)) { \
525 : properties.append(QStringLiteral(#property).toLower());\
526 : }
527 : /// \endcond
528 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(Broadcasting)
529 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(Read)
530 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(WriteNoResponse)
531 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(Write)
532 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(Notify)
533 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(Indicate)
534 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(WriteSigned)
535 0 : QTPOKIT_INTERNAL_TEST_AND_APPEND(ExtendedProperty)
536 : #undef QTPOKIT_INTERNAL_TEST_AND_APPEND
537 0 : qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" supports %3.)").arg(characteristic.uuid().toString(),
538 0 : PokitDevice::charcteristicToString(characteristic.uuid()), properties.join(QStringLiteral(", ")));
539 0 : }
540 : }
541 :
542 54 : if (newState == QLowEnergyService::
543 : #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
544 : ServiceDiscovered
545 : #else
546 : RemoteServiceDiscovered
547 : #endif
548 : ) {
549 : Q_Q(AbstractPokitService);
550 18 : qCDebug(lc).noquote() << tr("Service details discovered.");
551 18 : emit q->serviceDetailsDiscovered();
552 : }
553 54 : }
554 :
555 : /*!
556 : * Handles `QLowEnergyService::characteristicRead` events. This base implementation simply debug
557 : * logs the event.
558 : *
559 : * Derived classes should implement this function to handle the successful reads of
560 : * \a characteristic, typically by parsing \a value, then emitting a speciailised signal.
561 : */
562 126 : void AbstractPokitServicePrivate::characteristicRead(
563 : const QLowEnergyCharacteristic &characteristic, const QByteArray &value)
564 : {
565 126 : qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" read %n byte/s: %3)", nullptr, value.size()).arg(
566 0 : characteristic.uuid().toString(), PokitDevice::charcteristicToString(characteristic.uuid()), toHexString(value));
567 126 : }
568 :
569 : /*!
570 : * Handles `QLowEnergyService::characteristicWritten` events. This base implementation simply debug
571 : * logs the event.
572 : *
573 : * Derived classes should implement this function to handle the successful writes of
574 : * \a characteristic, typically by parsing \a newValue, then emitting a speciailised signal.
575 : */
576 126 : void AbstractPokitServicePrivate::characteristicWritten(
577 : const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
578 : {
579 126 : qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" written with %Ln byte/s: %3)", nullptr, newValue.size())
580 0 : .arg(characteristic.uuid().toString(), PokitDevice::charcteristicToString(characteristic.uuid()), toHexString(newValue));
581 126 : }
582 :
583 : /*!
584 : * Handles `QLowEnergyService::characteristicChanged` events. This base implementation simply debug
585 : * logs the event.
586 : *
587 : * If derived classes support characteristics with client-side notification (ie Notify, as opposed
588 : * to Read or Write operations), they should implement this function to handle the successful reads of
589 : * \a characteristic, typically by parsing \a value, then emitting a speciailised signal.
590 : */
591 72 : void AbstractPokitServicePrivate::characteristicChanged(
592 : const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
593 : {
594 72 : qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" changed to %Ln byte/s: %3)", nullptr, newValue.size())
595 0 : .arg(characteristic.uuid().toString(), PokitDevice::charcteristicToString(characteristic.uuid()), toHexString(newValue));
596 72 : }
597 :
598 : /// \endcond
|