LCOV - code coverage report
Current view: top level - src/lib - pokitdevice.cpp (source / functions) Hit Total Coverage
Project: Dokit Lines: 118 131 90.1 %
Version: Functions: 27 52 51.9 %

          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 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         560 : PokitDevice::PokitDevice(const QBluetoothDeviceInfo &deviceInfo, QObject *parent)
      44         560 :     : QObject(parent), d_ptr(new PokitDevicePrivate(this))
      45             : {
      46             :     Q_D(PokitDevice);
      47         560 :     d->setController(QLowEnergyController::createCentral(deviceInfo, this));
      48         560 : }
      49             : 
      50             : /*!
      51             :  * Constructs a new Pokit device controller wrapper for \a controller, with \a parent.
      52             :  */
      53         340 : PokitDevice::PokitDevice(QLowEnergyController *controller, QObject *parent)
      54         340 :     : QObject(parent), d_ptr(new PokitDevicePrivate(this))
      55             : {
      56             :     Q_D(PokitDevice);
      57         340 :     d->setController(controller);
      58         340 : }
      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             : {
      70             : 
      71           0 : }
      72             : /// \endcond
      73             : 
      74             : /*!
      75             :  * Destroys this PokitDevice object.
      76             :  */
      77        1460 : PokitDevice::~PokitDevice()
      78             : {
      79         900 :     delete d_ptr;
      80        1460 : }
      81             : 
      82             : /*!
      83             :  * Returns a non-const pointer to the controller used to access the Pokit device.
      84             :  */
      85        1040 : QLowEnergyController * PokitDevice::controller()
      86             : {
      87             :     Q_D(PokitDevice);
      88        1040 :     return d->controller;
      89             : }
      90             : 
      91             : /*!
      92             :  * Returns a const pointer to the controller used to access the Pokit device.
      93             :  */
      94          20 : const QLowEnergyController * PokitDevice::controller() const
      95             : {
      96             :     Q_D(const PokitDevice);
      97          20 :     return d->controller;
      98             : }
      99             : 
     100             : /// \cond
     101             : #define QTPOKIT_INTERNAL_GET_SERVICE(typeName, varName) \
     102             :     Q_D(PokitDevice);                                 \
     103             :     const QMutexLocker scopedLock(&d->varName##Mutex);\
     104             :     if (d->varName == nullptr) {                      \
     105             :         d->varName = new typeName(d->controller);     \
     106             :     }                                                 \
     107             :     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          80 : CalibrationService * PokitDevice::calibration()
     118             : {
     119         204 :     QTPOKIT_INTERNAL_GET_SERVICE(CalibrationService, calibration);
     120             : }
     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          80 : DataLoggerService * PokitDevice::dataLogger()
     130             : {
     131         204 :     QTPOKIT_INTERNAL_GET_SERVICE(DataLoggerService, dataLogger);
     132             : }
     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         440 : DeviceInfoService * PokitDevice::deviceInformation()
     143             : {
     144        1128 :     QTPOKIT_INTERNAL_GET_SERVICE(DeviceInfoService, deviceInfo);
     145             : }
     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          80 : DsoService * PokitDevice::dso()
     155             : {
     156         204 :     QTPOKIT_INTERNAL_GET_SERVICE(DsoService, dso);
     157             : }
     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          80 : MultimeterService * PokitDevice::multimeter()
     167             : {
     168         204 :     QTPOKIT_INTERNAL_GET_SERVICE(MultimeterService, multimeter);
     169             : }
     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         280 : StatusService * PokitDevice::status()
     179             : {
     180         714 :     QTPOKIT_INTERNAL_GET_SERVICE(StatusService, status);
     181             : }
     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         200 : QString PokitDevice::serviceToString(const QBluetoothUuid &uuid)
     191             : {
     192             :     static const QHash<QBluetoothUuid, QString> hash{
     193          26 :         { CalibrationService::serviceUuid, tr("Calibration") },
     194          20 :         { DataLoggerService::serviceUuid,  tr("Data Logger") },
     195          20 :         { DsoService::serviceUuid,         tr("DSO") },
     196          20 :         { MultimeterService::serviceUuid,  tr("Multimeter") },
     197          20 :         { StatusService::ServiceUuids::pokitMeter, tr("Status (Pokit Meter)") },
     198          20 :         { StatusService::ServiceUuids::pokitPro,   tr("Status (Pokit Pro)") },
     199             :         { DeviceInfoService::serviceUuid,
     200          40 :             QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid::DeviceInformation) },
     201             : 
     202             :         // The following are not specifically supported by this library, but strings provided for nicer debug output.
     203             :         { QBluetoothUuid::ServiceClassUuid::GenericAccess,
     204          40 :             QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid::GenericAccess) },
     205             :         { QBluetoothUuid::ServiceClassUuid::GenericAttribute,
     206          38 :             QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid::GenericAttribute) },
     207          39 :         { QBluetoothUuid(QStringLiteral("1d14d6ee-fd63-4fa1-bfa4-8f47b42119f0")), tr("OTA Firmware Update") },
     208         426 :     };
     209         220 :     return hash.value(uuid);
     210           0 : }
     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         440 : QString PokitDevice::charcteristicToString(const QBluetoothUuid &uuid)
     219             : {
     220             :     static const QHash<QBluetoothUuid, QString> hash{
     221          26 :         { CalibrationService::CharacteristicUuids::temperature, tr("Temperature") },
     222          20 :         { CalibrationService::CharacteristicUuids::getParam,    tr("Get Param") },
     223          20 :         { CalibrationService::CharacteristicUuids::setParam,    tr("Set Param") },
     224             : 
     225          20 :         { DataLoggerService::CharacteristicUuids::metadata, tr("Metadata") },
     226          20 :         { DataLoggerService::CharacteristicUuids::reading,  tr("Reading") },
     227          20 :         { DataLoggerService::CharacteristicUuids::settings, tr("Settings") },
     228             : 
     229          20 :         { DsoService::CharacteristicUuids::metadata, tr("Metadata") },
     230          20 :         { DsoService::CharacteristicUuids::reading,  tr("Reading") },
     231          20 :         { DsoService::CharacteristicUuids::settings, tr("Settings") },
     232             : 
     233          20 :         { MultimeterService::CharacteristicUuids::reading,  tr("Reading") },
     234          20 :         { MultimeterService::CharacteristicUuids::settings, tr("Settings") },
     235             : 
     236          20 :         { StatusService::CharacteristicUuids::deviceCharacteristics, tr("Device Characteristics") },
     237          20 :         { StatusService::CharacteristicUuids::flashLed,              tr("Flash LED") },
     238          20 :         { StatusService::CharacteristicUuids::name,                  tr("Name") },
     239          20 :         { StatusService::CharacteristicUuids::status,                tr("Status") },
     240          20 :         { StatusService::CharacteristicUuids::torch,                 tr("Torch") },
     241          20 :         { StatusService::CharacteristicUuids::buttonPress,           tr("Button Press") },
     242             : 
     243             :         { DeviceInfoService::CharacteristicUuids::firmwareRevision,
     244          40 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::FirmwareRevisionString) },
     245             :         { DeviceInfoService::CharacteristicUuids::hardwareRevision,
     246          40 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::HardwareRevisionString) },
     247             :         { DeviceInfoService::CharacteristicUuids::manufacturerName,
     248          40 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::ManufacturerNameString) },
     249             :         { DeviceInfoService::CharacteristicUuids::modelNumber,
     250          40 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::ModelNumberString) },
     251             :         { DeviceInfoService::CharacteristicUuids::softwareRevision,
     252          40 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::SoftwareRevisionString) },
     253             :         { DeviceInfoService::CharacteristicUuids::serialNumber,
     254          40 :             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          39 :         { QBluetoothUuid(QStringLiteral("f7bf3564-fb6d-4e53-88a4-5e37e0326063")), tr("OTA Control") },
     258          39 :         { QBluetoothUuid(QStringLiteral("984227f3-34fc-4045-a5d0-2c581f81a153")), tr("OTA Data Transfer") },
     259         966 :     };
     260         484 :     return hash.value(uuid);
     261           0 : }
     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         900 : PokitDevicePrivate::PokitDevicePrivate(PokitDevice * const q) : q_ptr(q)
     274             : {
     275             : 
     276         900 : }
     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        1040 : void PokitDevicePrivate::setController(QLowEnergyController * newController)
     292             : {
     293        1040 :     if (newController == this->controller) {
     294         418 :         qCDebug(lc).noquote() << tr("Controller already set to:") << newController;
     295         342 :         return;
     296             :     }
     297             : 
     298         660 :     if (this->controller) {
     299          66 :         qCDebug(lc).noquote() << tr("Disconnecting signals from previous controller:")
     300           0 :             << controller;
     301          60 :         disconnect(this->controller, nullptr, this, nullptr);
     302             :     }
     303             : 
     304         738 :     qCDebug(lc).noquote() << tr("Setting new controller:") << newController;
     305         660 :     this->controller = newController;
     306         660 :     if (!newController) {
     307             :         return; // Don't bother continuing to connect if new controller is null.
     308             :     }
     309             : 
     310         694 :     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         620 :     connect(controller, &QLowEnergyController::connected,
     315          37 :             this, &PokitDevicePrivate::connected);
     316             : 
     317         620 :     connect(controller, &QLowEnergyController::connectionUpdated,
     318          37 :             this, &PokitDevicePrivate::connectionUpdated);
     319             : 
     320         620 :     connect(controller, &QLowEnergyController::disconnected,
     321          37 :             this, &PokitDevicePrivate::disconnected);
     322             : 
     323         620 :     connect(controller, &QLowEnergyController::discoveryFinished,
     324          37 :             this, &PokitDevicePrivate::discoveryFinished);
     325             : 
     326             : 
     327         620 :     connect(controller,
     328             :     #if (QT_VERSION < QT_VERSION_CHECK(6, 2, 0))
     329             :         QOverload<QLowEnergyController::Error>::of(&QLowEnergyController::error),
     330             :     #else
     331             :         &QLowEnergyController::errorOccurred,
     332             :     #endif
     333          37 :         this, &PokitDevicePrivate::errorOccurred);
     334             : 
     335             : 
     336         620 :     connect(controller, &QLowEnergyController::serviceDiscovered,
     337          37 :             this, &PokitDevicePrivate::serviceDiscovered);
     338             : 
     339         620 :     connect(controller, &QLowEnergyController::stateChanged,
     340          74 :             this, &PokitDevicePrivate::stateChanged);
     341             : }
     342             : 
     343             : /*!
     344             :  * Handle connected signals.
     345             :  */
     346          60 : void PokitDevicePrivate::connected() const
     347             : {
     348          60 :     if (controller == nullptr) {
     349          96 :         qCCritical(lc).noquote() << tr("PokitDevicePrivate::connected slot invoked without a controller.");
     350          36 :         return; // Just to avoid the nullptr dereference below.
     351             :     }
     352          22 :     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             : }
     356             : 
     357             : /*!
     358             :  * Handle connectionUpdated signals.
     359             :  */
     360          20 : void PokitDevicePrivate::connectionUpdated(const QLowEnergyConnectionParameters &newParameters) const
     361             : {
     362          22 :     qCDebug(lc).noquote() << tr("Connection updated:") << newParameters.latency()
     363           0 :         << newParameters.minimumInterval() << newParameters.maximumInterval()
     364           0 :         << newParameters.supervisionTimeout();
     365          20 : }
     366             : 
     367             : /*!
     368             :  * Handle disconnected signals.
     369             :  */
     370          20 : void PokitDevicePrivate::disconnected() const
     371             : {
     372          22 :     qCDebug(lc).noquote() << tr("Device disconnected.");
     373          20 : }
     374             : 
     375             : /*!
     376             :  * Handle discoveryFinished signals.
     377             :  */
     378          20 : void PokitDevicePrivate::discoveryFinished() const
     379             : {
     380          22 :     qCDebug(lc).noquote() << tr("Service discovery finished.");
     381          20 : }
     382             : 
     383             : /*!
     384             :  * Handle error signals.
     385             :  */
     386          20 : void PokitDevicePrivate::errorOccurred(QLowEnergyController::Error newError) const
     387             : {
     388          22 :     qCDebug(lc).noquote() << tr("Controller error:") << newError;
     389          20 : }
     390             : 
     391             : /*!
     392             :  * Handle serviceDiscovered signals.
     393             :  */
     394          20 : void PokitDevicePrivate::serviceDiscovered(const QBluetoothUuid &newService) const
     395             : {
     396          22 :     qCDebug(lc).noquote() << tr(R"(Service discovered: %1 "%2")")
     397           0 :         .arg(newService.toString(), PokitDevice::serviceToString(newService));
     398          20 : }
     399             : 
     400             : /*!
     401             :  * Handle stateChanged signals.
     402             :  */
     403          20 : void PokitDevicePrivate::stateChanged(QLowEnergyController::ControllerState state) const
     404             : {
     405          22 :     qCDebug(lc).noquote() << tr("State changed to:") << state;
     406          20 : }
     407             : 
     408             : /// \endcond

Generated by: LCOV version 1.14