LCOV - code coverage report
Current view: top level - src/lib - pokitdevice.cpp (source / functions) Coverage Total Hit
Project: Dokit Lines: 93.8 % 194 182
Version: Functions: 95.7 % 23 22

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2022-2025 Paul Colby <git@colby.id.au>
       2              : // SPDX-License-Identifier: LGPL-3.0-or-later
       3              : 
       4              : /*!
       5              :  * \file
       6              :  * Defines the PokitDevice and PokitDevicePrivate classes.
       7              :  */
       8              : 
       9              : #include <qtpokit/pokitdevice.h>
      10              : 
      11              : #include <qtpokit/calibrationservice.h>
      12              : #include <qtpokit/dataloggerservice.h>
      13              : #include <qtpokit/deviceinfoservice.h>
      14              : #include <qtpokit/dsoservice.h>
      15              : #include <qtpokit/multimeterservice.h>
      16              : #include <qtpokit/statusservice.h>
      17              : 
      18              : #include "pokitdevice_p.h"
      19              : 
      20              : #include <QMutexLocker>
      21              : 
      22              : /*!
      23              :  * \class PokitDevice
      24              :  *
      25              :  * The PokitDevice class simplifies Pokit device access.
      26              :  *
      27              :  * It does this by wrapping QLowEnergyController to provide:
      28              :  * * convenient Pokit service factory methods (dataLogger(), deviceInformation(), dso(),
      29              :      multimeter() and status()); and
      30              :  * * consistent debug logging of QLowEnergyController events.
      31              :  *
      32              :  * But this class is entirely optional, in that all features of all other QtPokit classes can be
      33              :  * used wihtout this class.  It's just a (meaningful) convenience.
      34              :  */
      35              : 
      36              : /*!
      37              :  * Constructs a new Pokit device controller wrapper for \a deviceInfo, with \a parent.
      38              :  *
      39              :  * Though not strictly necessary, \a deviceInfo should normally come from a
      40              :  * PokitDiscoveryAgent instance (or a QBluetoothDeviceDiscoveryAgent), otherwise connection
      41              :  * is likely to fail with QLowEnergyController::UnknownRemoteDeviceError.
      42              :  */
      43         1530 : PokitDevice::PokitDevice(const QBluetoothDeviceInfo &deviceInfo, QObject *parent)
      44         2278 :     : QObject(parent), d_ptr(new PokitDevicePrivate(this))
      45          748 : {
      46          748 :     Q_D(PokitDevice);
      47         2278 :     d->setController(QLowEnergyController::createCentral(deviceInfo, this));
      48         2278 : }
      49              : 
      50              : /*!
      51              :  * Constructs a new Pokit device controller wrapper for \a controller, with \a parent.
      52              :  */
      53          765 : PokitDevice::PokitDevice(QLowEnergyController *controller, QObject *parent)
      54         1139 :     : QObject(parent), d_ptr(new PokitDevicePrivate(this))
      55          824 : {
      56          824 :     Q_D(PokitDevice);
      57         1589 :     d->setController(controller);
      58         1589 : }
      59              : 
      60              : /*!
      61              :  * \cond internal
      62              :  * Constructs a new Pokit device controller wrapper with \a parent, and private implementation \a d.
      63              :  *
      64              :  * Derived classes using this constructor should use PokitDevicePrivate::setController to assign
      65              :  * the BLE controller as some point.
      66              :  */
      67            0 : PokitDevice::PokitDevice(PokitDevicePrivate * const d, QObject * const parent)
      68            0 :     : QObject(parent), d_ptr(d)
      69            0 : {
      70              : 
      71            0 : }
      72              : /// \endcond
      73              : 
      74              : /*!
      75              :  * Destroys this PokitDevice object.
      76              :  */
      77         3825 : PokitDevice::~PokitDevice()
      78         1572 : {
      79         3867 :     delete d_ptr;
      80         5397 : }
      81              : 
      82              : /*!
      83              :  * Returns a non-const pointer to the controller used to access the Pokit device.
      84              :  */
      85         2880 : QLowEnergyController * PokitDevice::controller()
      86         1438 : {
      87         1438 :     Q_D(PokitDevice);
      88         4318 :     return d->controller;
      89         1438 : }
      90              : 
      91              : /*!
      92              :  * Returns a const pointer to the controller used to access the Pokit device.
      93              :  */
      94           45 : const QLowEnergyController * PokitDevice::controller() const
      95           52 : {
      96           52 :     Q_D(const PokitDevice);
      97           97 :     return d->controller;
      98           52 : }
      99              : 
     100              : /// \cond
     101              : #define QTPOKIT_INTERNAL_GET_SERVICE(typeName, varName) \
     102         1996 :     Q_D(PokitDevice);                                 \
     103         1996 :     const QMutexLocker scopedLock(&d->varName##Mutex);\
     104         1996 :     if (d->varName == nullptr) {                      \
     105         1060 :         d->varName = new typeName(d->controller);     \
     106         1060 :     }                                                 \
     107         1996 :     return d->varName                                 \
     108              : /// \endcond
     109              : 
     110              : /*!
     111              :  * Returns a pointer to a CalibrationService instance that uses this device's controller for access.
     112              :  *
     113              :  * This is a convenience function, that always returns the same pointer (for this PokitDevice
     114              :  * instance), but the service itself is lazily created (in a threadsafe manner) on the first
     115              :  * invocation of this function.
     116              :  */
     117          180 : CalibrationService * PokitDevice::calibration()
     118          208 : {
     119          644 :     QTPOKIT_INTERNAL_GET_SERVICE(CalibrationService, calibration);
     120          208 : }
     121              : 
     122              : /*!
     123              :  * Returns a pointer to a DataLoggerService instance that uses this device's controller for access.
     124              :  *
     125              :  * This is a convenience function, that always returns the same pointer (for this PokitDevice
     126              :  * instance), but the service itself is lazily created (in a threadsafe manner) on the first
     127              :  * invocation of this function.
     128              :  */
     129          180 : DataLoggerService * PokitDevice::dataLogger()
     130          208 : {
     131          644 :     QTPOKIT_INTERNAL_GET_SERVICE(DataLoggerService, dataLogger);
     132          208 : }
     133              : 
     134              : /*!
     135              :  * Returns a pointer to DeviceInformationService instance that uses this device's controller for
     136              :  * access.
     137              :  *
     138              :  * This is a convenience function, that always returns the same pointer (for this PokitDevice
     139              :  * instance), but the service itself is lazily created (in a threadsafe manner) on the first
     140              :  * invocation of this function.
     141              :  */
     142         1260 : DeviceInfoService * PokitDevice::deviceInformation()
     143          736 : {
     144         3788 :     QTPOKIT_INTERNAL_GET_SERVICE(DeviceInfoService, deviceInfo);
     145          736 : }
     146              : 
     147              : /*!
     148              :  * Returns a pointer to DsoService instance that uses this device's controller for access.
     149              :  *
     150              :  * This is a convenience function, that always returns the same pointer (for this PokitDevice
     151              :  * instance), but the service itself is lazily created (in a threadsafe manner) on the first
     152              :  * invocation of this function.
     153              :  */
     154          180 : DsoService * PokitDevice::dso()
     155          208 : {
     156          644 :     QTPOKIT_INTERNAL_GET_SERVICE(DsoService, dso);
     157          208 : }
     158              : 
     159              : /*!
     160              :  * Returns a pointer to MultimeterService instance that uses this device's controller for access.
     161              :  *
     162              :  * This is a convenience function, that always returns the same pointer (for this PokitDevice
     163              :  * instance), but the service itself is lazily created (in a threadsafe manner) on the first
     164              :  * invocation of this function.
     165              :  */
     166          180 : MultimeterService * PokitDevice::multimeter()
     167          208 : {
     168          644 :     QTPOKIT_INTERNAL_GET_SERVICE(MultimeterService, multimeter);
     169          208 : }
     170              : 
     171              : /*!
     172              :  * Returns a pointer to StatusService instance that uses this device's controller for access.
     173              :  *
     174              :  * This is a convenience function, that always returns the same pointer (for this PokitDevice
     175              :  * instance), but the service itself is lazily created (in a threadsafe manner) on the first
     176              :  * invocation of this function.
     177              :  */
     178          630 : StatusService * PokitDevice::status()
     179          428 : {
     180         1954 :     QTPOKIT_INTERNAL_GET_SERVICE(StatusService, status);
     181          428 : }
     182              : #undef QTPOKIT_INTERNAL_GET_SERVICE
     183              : 
     184              : /*!
     185              :  * Returns a human-readable name for the \a uuid service, or a null QString if unknonw.
     186              :  *
     187              :  * This is equivalent to QBluetoothUuid::serviceClassToString() but for services provided by Pokit
     188              :  * devices.
     189              :  */
     190          450 : QString PokitDevice::serviceToString(const QBluetoothUuid &uuid)
     191          520 : {
     192          520 :     static const QHash<QBluetoothUuid, QString> hash{
     193          589 :         { CalibrationService::serviceUuid, tr("Calibration") },
     194          565 :         { DataLoggerService::serviceUuid,  tr("Data Logger") },
     195          565 :         { DsoService::serviceUuid,         tr("DSO") },
     196          565 :         { MultimeterService::serviceUuid,  tr("Multimeter") },
     197          565 :         { StatusService::ServiceUuids::pokitMeter, tr("Status (Pokit Meter)") },
     198          565 :         { StatusService::ServiceUuids::pokitPro,   tr("Status (Pokit Pro)") },
     199          520 :         { DeviceInfoService::serviceUuid,
     200          610 :             QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid::DeviceInformation) },
     201              : 
     202              :         // The following are not specifically supported by this library, but strings provided for nicer debug output.
     203          520 :         { QBluetoothUuid::ServiceClassUuid::GenericAccess,
     204          610 :             QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid::GenericAccess) },
     205          520 :         { QBluetoothUuid::ServiceClassUuid::GenericAttribute,
     206          609 :             QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid::GenericAttribute) },
     207          599 :         { QBluetoothUuid(QStringLiteral("1d14d6ee-fd63-4fa1-bfa4-8f47b42119f0")), tr("OTA Firmware Update") },
     208         1514 :     };
     209          990 :     return hash.value(uuid);
     210          520 : }
     211              : 
     212              : /*!
     213              :  * Returns a human-readable name for the \a uuid characteristic, or a null QString if unknown.
     214              :  *
     215              :  * This is equivalent to QBluetoothUuid::characteristicToString() but for characteristics provided
     216              :  * by Pokit devices.
     217              :  */
     218          990 : QString PokitDevice::charcteristicToString(const QBluetoothUuid &uuid)
     219         1144 : {
     220         1144 :     static const QHash<QBluetoothUuid, QString> hash{
     221         1213 :         { CalibrationService::CharacteristicUuids::temperature, tr("Temperature") },
     222         1189 :         { CalibrationService::CharacteristicUuids::getParam,    tr("Get Param") },
     223         1189 :         { CalibrationService::CharacteristicUuids::setParam,    tr("Set Param") },
     224              : 
     225         1189 :         { DataLoggerService::CharacteristicUuids::metadata, tr("Metadata") },
     226         1189 :         { DataLoggerService::CharacteristicUuids::reading,  tr("Reading") },
     227         1189 :         { DataLoggerService::CharacteristicUuids::settings, tr("Settings") },
     228              : 
     229         1189 :         { DsoService::CharacteristicUuids::metadata, tr("Metadata") },
     230         1189 :         { DsoService::CharacteristicUuids::reading,  tr("Reading") },
     231         1189 :         { DsoService::CharacteristicUuids::settings, tr("Settings") },
     232              : 
     233         1189 :         { MultimeterService::CharacteristicUuids::reading,  tr("Reading") },
     234         1189 :         { MultimeterService::CharacteristicUuids::settings, tr("Settings") },
     235              : 
     236         1189 :         { StatusService::CharacteristicUuids::deviceCharacteristics, tr("Device Characteristics") },
     237         1189 :         { StatusService::CharacteristicUuids::flashLed,              tr("Flash LED") },
     238         1189 :         { StatusService::CharacteristicUuids::name,                  tr("Name") },
     239         1189 :         { StatusService::CharacteristicUuids::status,                tr("Status") },
     240         1189 :         { StatusService::CharacteristicUuids::torch,                 tr("Torch") },
     241         1189 :         { StatusService::CharacteristicUuids::buttonPress,           tr("Button Press") },
     242              : 
     243         1144 :         { DeviceInfoService::CharacteristicUuids::firmwareRevision,
     244         1234 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::FirmwareRevisionString) },
     245         1144 :         { DeviceInfoService::CharacteristicUuids::hardwareRevision,
     246         1234 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::HardwareRevisionString) },
     247         1144 :         { DeviceInfoService::CharacteristicUuids::manufacturerName,
     248         1234 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::ManufacturerNameString) },
     249         1144 :         { DeviceInfoService::CharacteristicUuids::modelNumber,
     250         1234 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::ModelNumberString) },
     251         1144 :         { DeviceInfoService::CharacteristicUuids::softwareRevision,
     252         1234 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::SoftwareRevisionString) },
     253         1144 :         { DeviceInfoService::CharacteristicUuids::serialNumber,
     254         1234 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::SerialNumberString) },
     255              : 
     256              :         // The next two are not specifically supported by this library, but strings provided for nicer debug output.
     257         1223 :         { QBluetoothUuid(QStringLiteral("f7bf3564-fb6d-4e53-88a4-5e37e0326063")), tr("OTA Control") },
     258         1223 :         { QBluetoothUuid(QStringLiteral("984227f3-34fc-4045-a5d0-2c581f81a153")), tr("OTA Data Transfer") },
     259         3353 :     };
     260         2178 :     return hash.value(uuid);
     261         1144 : }
     262              : 
     263              : /*!
     264              :  * \cond internal
     265              :  * \class PokitDevicePrivate
     266              :  *
     267              :  * The PokitDevicePrivate class provides private implementation for PokitDevice.
     268              :  */
     269              : 
     270              : /*!
     271              :  * Constructs a new PokitDevicePrivate object with public implementation \a q.
     272              :  */
     273         3417 : PokitDevicePrivate::PokitDevicePrivate(PokitDevice * const q) : q_ptr(q)
     274         1572 : {
     275              : 
     276         3867 : }
     277              : 
     278              : /*!
     279              :  * Sets \a newController to be used for accessing Pokit devices.
     280              :  *
     281              :  * If a controller has already been set (and is not the same pointer), then the previous controller
     282              :  * will be disconnected, and replaced with \a newController.
     283              :  *
     284              :  * This function will not take ownership of the new controller. The caller is responsible for
     285              :  * ensuring that \a newContorller remains valid for the lifetime of this instance, or until this
     286              :  * function is used again to replace \a newController with another one (which may be a nullptr).
     287              :  *
     288              :  * \see controller
     289              :  * \see PokitDevice::controller()
     290              :  */
     291         2610 : void PokitDevicePrivate::setController(QLowEnergyController * newController)
     292         1726 : {
     293         4336 :     if (newController == this->controller) {
     294         1970 :         qCDebug(lc).noquote() << tr("Controller already set to:") << newController;
     295         1476 :         return;
     296          868 :     }
     297              : 
     298         2613 :     if (this->controller) {
     299          240 :         qCDebug(lc).noquote() << tr("Disconnecting signals from previous controller:")
     300            0 :             << controller;
     301          201 :         disconnect(this->controller, nullptr, this, nullptr);
     302           66 :     }
     303              : 
     304         3120 :     qCDebug(lc).noquote() << tr("Setting new controller:") << newController;
     305         2613 :     this->controller = newController;
     306         2613 :     if (!newController) {
     307           44 :         return; // Don't bother continuing to connect if new controller is null.
     308           44 :     }
     309              : 
     310         2960 :     qCDebug(lc).noquote() << tr(R"(Set new controller "%1" (%2) at (%3).)").arg(
     311            0 :         controller->remoteName(), controller->remoteDeviceUuid().toString(),
     312            0 :         controller->remoteAddress().toString());
     313              : 
     314         2479 :     connect(controller, &QLowEnergyController::connected,
     315         1221 :             this, &PokitDevicePrivate::connected);
     316              : 
     317         2479 :     connect(controller, &QLowEnergyController::connectionUpdated,
     318         1221 :             this, &PokitDevicePrivate::connectionUpdated);
     319              : 
     320         2479 :     connect(controller, &QLowEnergyController::disconnected,
     321         1221 :             this, &PokitDevicePrivate::disconnected);
     322              : 
     323         2479 :     connect(controller, &QLowEnergyController::discoveryFinished,
     324         1221 :             this, &PokitDevicePrivate::discoveryFinished);
     325              : 
     326              : 
     327         2479 :     connect(controller,
     328          333 :     #if (QT_VERSION < QT_VERSION_CHECK(6, 2, 0))
     329          333 :         QOverload<QLowEnergyController::Error>::of(&QLowEnergyController::error),
     330              :     #else
     331          481 :         &QLowEnergyController::errorOccurred,
     332          481 :     #endif
     333         1221 :         this, &PokitDevicePrivate::errorOccurred);
     334              : 
     335              : 
     336         2479 :     connect(controller, &QLowEnergyController::serviceDiscovered,
     337         1221 :             this, &PokitDevicePrivate::serviceDiscovered);
     338              : 
     339         2479 :     connect(controller, &QLowEnergyController::stateChanged,
     340         1628 :             this, &PokitDevicePrivate::stateChanged);
     341          814 : }
     342              : 
     343              : /*!
     344              :  * Handle connected signals.
     345              :  */
     346          135 : void PokitDevicePrivate::connected() const
     347           66 : {
     348          201 :     if (controller == nullptr) {
     349          292 :         qCCritical(lc).noquote() << tr("PokitDevicePrivate::connected slot invoked without a controller.");
     350          108 :         return; // Just to avoid the nullptr dereference below.
     351           44 :     }
     352           80 :     qCDebug(lc).noquote() << tr(R"(Connected to "%1" (%2) at (%3).)").arg(
     353            0 :         controller->remoteName(), controller->remoteDeviceUuid().toString(),
     354            0 :         controller->remoteAddress().toString());
     355           22 : }
     356              : 
     357              : /*!
     358              :  * Handle connectionUpdated signals.
     359              :  */
     360           45 : void PokitDevicePrivate::connectionUpdated(const QLowEnergyConnectionParameters &newParameters) const
     361           52 : {
     362          110 :     qCDebug(lc).noquote() << tr("Connection updated:") << newParameters.latency()
     363            0 :         << newParameters.minimumInterval() << newParameters.maximumInterval()
     364            0 :         << newParameters.supervisionTimeout();
     365           97 : }
     366              : 
     367              : /*!
     368              :  * Handle disconnected signals.
     369              :  */
     370           45 : void PokitDevicePrivate::disconnected() const
     371           52 : {
     372          110 :     qCDebug(lc).noquote() << tr("Device disconnected.");
     373           97 : }
     374              : 
     375              : /*!
     376              :  * Handle discoveryFinished signals.
     377              :  */
     378           45 : void PokitDevicePrivate::discoveryFinished() const
     379           52 : {
     380          110 :     qCDebug(lc).noquote() << tr("Service discovery finished.");
     381           97 : }
     382              : 
     383              : /*!
     384              :  * Handle error signals.
     385              :  */
     386           45 : void PokitDevicePrivate::errorOccurred(QLowEnergyController::Error newError) const
     387           52 : {
     388          110 :     qCDebug(lc).noquote() << tr("Controller error:") << newError;
     389           97 : }
     390              : 
     391              : /*!
     392              :  * Handle serviceDiscovered signals.
     393              :  */
     394           45 : void PokitDevicePrivate::serviceDiscovered(const QBluetoothUuid &newService) const
     395           52 : {
     396          110 :     qCDebug(lc).noquote() << tr(R"(Service discovered: %1 "%2")")
     397            0 :         .arg(newService.toString(), PokitDevice::serviceToString(newService));
     398           97 : }
     399              : 
     400              : /*!
     401              :  * Handle stateChanged signals.
     402              :  */
     403           45 : void PokitDevicePrivate::stateChanged(QLowEnergyController::ControllerState state) const
     404           52 : {
     405          110 :     qCDebug(lc).noquote() << tr("State changed to:") << state;
     406           97 : }
     407              : 
     408              : /// \endcond
        

Generated by: LCOV version 2.3-1