LCOV - code coverage report
Current view: top level - src/lib - abstractpokitservice.cpp (source / functions) Hit Total Coverage
Project: Dokit Lines: 100 168 59.5 %
Version: Functions: 23 28 82.1 %

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

Generated by: LCOV version 1.14