LCOV - code coverage report
Current view: top level - src/lib - abstractpokitservice.cpp (source / functions) Hit Total Coverage
Project: Dokit Lines: 116 195 59.5 %
Version: Functions: 25 56 44.6 %

          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        7700 : AbstractPokitService::AbstractPokitService(
      28        7700 :     AbstractPokitServicePrivate * const d, QObject * const parent)
      29        7700 :     : QObject(parent), d_ptr(d)
      30             : {
      31             : 
      32        7700 : }
      33             : /// \endcond
      34             : 
      35             : /*!
      36             :  * Destroys this AbstractPokitService object.
      37             :  */
      38        2920 : AbstractPokitService::~AbstractPokitService()
      39             : {
      40        2920 :     delete d_ptr;
      41        2920 : }
      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          60 : bool AbstractPokitService::autoDiscover() const
      61             : {
      62             :     Q_D(const AbstractPokitService);
      63          60 :     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          40 : void AbstractPokitService::setAutoDiscover(const bool discover)
      76             : {
      77             :     Q_D(AbstractPokitService);
      78          40 :     d->autoDiscover = discover;
      79          40 : }
      80             : 
      81             : /*!
      82             :  * Returns the Pokit product this service is attached to.
      83             :  *
      84             :  * \see setPokitProduct
      85             :  */
      86        5700 : std::optional<PokitProduct> AbstractPokitService::pokitProduct() const
      87             : {
      88             :     Q_D(const AbstractPokitService);
      89        5700 :     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        4980 : void AbstractPokitService::setPokitProduct(const PokitProduct product)
     111             : {
     112             :     Q_D(AbstractPokitService);
     113        4980 :     d->pokitProduct = product;
     114        4980 : }
     115             : 
     116             : /*!
     117             :  * Returns a non-const pointer to the internal service object, if any.
     118             :  */
     119         180 : QLowEnergyService * AbstractPokitService::service()
     120             : {
     121             :     Q_D(AbstractPokitService);
     122         180 :     return d->service;
     123             : }
     124             : 
     125             : /*!
     126             :  * Returns a const pointer to the internal service object, if any.
     127             :  */
     128          20 : const QLowEnergyService * AbstractPokitService::service() const
     129             : {
     130             :     Q_D(const AbstractPokitService);
     131          20 :     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        7700 : AbstractPokitServicePrivate::AbstractPokitServicePrivate(const QBluetoothUuid &serviceUuid,
     169        7700 :     QLowEnergyController * controller, AbstractPokitService * const q)
     170        7700 :     : controller(controller), serviceUuid(serviceUuid), q_ptr(q)
     171             : {
     172        7700 :     if (controller) {
     173        4740 :         connect(controller, &QLowEnergyController::connected,
     174         243 :                 this, &AbstractPokitServicePrivate::connected);
     175             : 
     176        4740 :         connect(controller, &QLowEnergyController::discoveryFinished,
     177         243 :                 this, &AbstractPokitServicePrivate::discoveryFinished);
     178             : 
     179        4740 :         connect(controller, &QLowEnergyController::serviceDiscovered,
     180         243 :                 this, &AbstractPokitServicePrivate::serviceDiscovered);
     181             : 
     182        4740 :         createServiceObject();
     183             :     }
     184        7700 : }
     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        4900 : bool AbstractPokitServicePrivate::createServiceObject()
     194             : {
     195        4900 :     if (!controller) {
     196             :         return false;
     197             :     }
     198             : 
     199        4820 :     if (service) {
     200          22 :         qCDebug(lc).noquote() << tr("Already have service object:") << service;
     201          18 :         return true;
     202             :     }
     203             : 
     204        4800 :     if (serviceUuid.isNull()) {
     205         220 :         qCDebug(lc).noquote() << tr("Service UUID not assigned yet.");
     206         180 :         return false;
     207             :     }
     208             : 
     209        4600 :     service = controller->createServiceObject(serviceUuid, this);
     210        4600 :     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           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             :             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             :             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           0 :         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        4460 : QLowEnergyCharacteristic AbstractPokitServicePrivate::getCharacteristic(const QBluetoothUuid &uuid) const
     266             : {
     267        4460 :     if (!service) {
     268        4978 :         qCDebug(lc).noquote() << tr(R"(Characteristic %1 "%2" requested before service assigned.)")
     269           0 :             .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
     270        4460 :         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"(Characteristic %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"(Characteristic %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         520 : bool AbstractPokitServicePrivate::readCharacteristic(const QBluetoothUuid &uuid)
     311             : {
     312         520 :     const QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
     313         520 :     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         520 : }
     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         180 : bool AbstractPokitServicePrivate::enableCharacteristicNotificatons(const QBluetoothUuid &uuid)
     331             : {
     332         198 :     qCDebug(lc).noquote() << tr(R"(Enabling CCCD for characteristic %1 "%2".)")
     333           0 :         .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
     334         180 :     QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
     335         180 :     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"(Characteristic %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         180 : }
     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         180 : bool AbstractPokitServicePrivate::disableCharacteristicNotificatons(const QBluetoothUuid &uuid)
     366             : {
     367         198 :     qCDebug(lc).noquote() << tr(R"(Disabling CCCD for characteristic %1 "%2".)")
     368           0 :         .arg(uuid.toString(), PokitDevice::charcteristicToString(uuid));
     369         180 :     QLowEnergyCharacteristic characteristic = getCharacteristic(uuid);
     370         180 :     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"(Characteristic %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         180 : }
     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         840 : bool AbstractPokitServicePrivate::checkSize(const QString &label, const QByteArray &data,
     401             :                                             const int minSize, const int maxSize,
     402             :                                             const bool failOnMax)
     403             : {
     404         840 :     if (data.size() < minSize) {
     405         546 :         qCWarning(lc).noquote() << tr("%1 requires %n byte/s, but only %2 present: %3", nullptr, minSize)
     406         520 :             .arg(label).arg(data.size()).arg(toHexString(data));
     407         234 :         return false;
     408             :     }
     409         580 :     if ((maxSize >= 0) && (data.size() > maxSize)) {
     410         280 :         qCWarning(lc).noquote() << tr("%1 has %n extraneous byte/s: %2", nullptr, data.size()-maxSize)
     411         175 :             .arg(label, toHexString(data.mid(maxSize)));
     412         100 :         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         520 : QString AbstractPokitServicePrivate::toHexString(const QByteArray &data, const int maxSize)
     426             : {
     427         156 :     return (data.size() <= maxSize)
     428        1260 :         ? QString::fromLatin1("0x%1").arg(QLatin1String(data.toHex(',')))
     429          60 :         : QString::fromLatin1("0x%1,...,%2").arg(
     430         598 :             QLatin1String(data.left(maxSize/2-1).toHex(',')),
     431        2480 :             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          40 : void AbstractPokitServicePrivate::connected()
     442             : {
     443          40 :     if (!controller) {
     444          42 :         qCWarning(lc).noquote() << tr("Connected with no controller set") << sender();
     445          18 :         return;
     446             :     }
     447             : 
     448          22 :     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          20 :     if (autoDiscover) {
     452          20 :         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          40 : void AbstractPokitServicePrivate::discoveryFinished()
     463             : {
     464          40 :     if (!controller) {
     465          42 :         qCWarning(lc).noquote() << tr("Discovery finished with no controller set") << sender();
     466          18 :         return;
     467             :     }
     468             : 
     469          22 :     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          20 :     if (!createServiceObject()) {
     474          48 :         qCWarning(lc).noquote() << tr("Discovery finished, but service not found.");
     475             :         Q_Q(AbstractPokitService);
     476          20 :         Q_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          20 : void AbstractPokitServicePrivate::errorOccurred(const QLowEnergyService::ServiceError newError)
     486             : {
     487             :     Q_Q(AbstractPokitService);
     488          22 :     qCDebug(lc).noquote() << tr("Service error") << newError;
     489          20 :     Q_EMIT q->serviceErrorOccurred(newError);
     490          20 : }
     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         120 : void AbstractPokitServicePrivate::serviceDiscovered(const QBluetoothUuid &newService)
     500             : {
     501         120 :     if ((!service) && (newService == serviceUuid)) {
     502          66 :         qCDebug(lc).noquote() << tr("Service discovered") << newService;
     503          60 :         createServiceObject();
     504             :     }
     505         120 : }
     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          60 : void AbstractPokitServicePrivate::stateChanged(QLowEnergyService::ServiceState newState)
     516             : {
     517          66 :     qCDebug(lc).noquote() << tr("State changed to") << newState;
     518             : 
     519          60 :     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          60 :     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          22 :         qCDebug(lc).noquote() << tr("Service details discovered.");
     551          20 :         Q_EMIT q->serviceDetailsDiscovered();
     552             :     }
     553          60 : }
     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         120 : void AbstractPokitServicePrivate::characteristicRead(
     563             :     const QLowEnergyCharacteristic &characteristic, const QByteArray &value)
     564             : {
     565         132 :     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         120 : }
     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         120 : void AbstractPokitServicePrivate::characteristicWritten(
     577             :     const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
     578             : {
     579         132 :     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         120 : }
     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          80 : void AbstractPokitServicePrivate::characteristicChanged(
     592             :     const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
     593             : {
     594          88 :     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          80 : }
     597             : 
     598             : /// \endcond

Generated by: LCOV version 1.14