LCOV - code coverage report
Current view: top level - src/lib - pokitdevice.cpp (source / functions) Hit Total Coverage
Project: QtPokit Lines: 110 121 90.9 %
Version: Functions: 28 30 93.3 %

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

Generated by: LCOV version 1.14