LCOV - code coverage report
Current view: top level - src/lib - pokitdevice.cpp (source / functions) Hit Total Coverage
Project: Dokit Lines: 106 119 89.1 %
Version: Functions: 28 30 93.3 %

          Line data    Source code
       1             : // SPDX-FileCopyrightText: 2022-2023 Paul Colby <git@colby.id.au>
       2             : // SPDX-License-Identifier: LGPL-3.0-or-later
       3             : 
       4             : /*!
       5             :  * \file
       6             :  * Defines the 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         492 : PokitDevice::PokitDevice(const QBluetoothDeviceInfo &deviceInfo, QObject *parent)
      45         492 :     : QObject(parent), d_ptr(new PokitDevicePrivate(this))
      46             : {
      47             :     Q_D(PokitDevice);
      48         492 :     d->setController(QLowEnergyController::createCentral(deviceInfo, this));
      49         492 : }
      50             : 
      51             : /*!
      52             :  * Constructs a new Pokit device controller wrapper for \a controller, with \a parent.
      53             :  */
      54         324 : PokitDevice::PokitDevice(QLowEnergyController *controller, QObject *parent)
      55         324 :     : QObject(parent), d_ptr(new PokitDevicePrivate(this))
      56             : {
      57             :     Q_D(PokitDevice);
      58         324 :     d->setController(controller);
      59         324 : }
      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        1308 : PokitDevice::~PokitDevice()
      79             : {
      80         816 :     delete d_ptr;
      81        1308 : }
      82             : 
      83             : /*!
      84             :  * Returns a non-const pointer to the controller used to access the Pokit device.
      85             :  */
      86         912 : QLowEnergyController * PokitDevice::controller()
      87             : {
      88             :     Q_D(PokitDevice);
      89         912 :     return d->controller;
      90             : }
      91             : 
      92             : /*!
      93             :  * Returns a const pointer to the controller used to access the Pokit device.
      94             :  */
      95          18 : const QLowEnergyController * PokitDevice::controller() const
      96             : {
      97             :     Q_D(const PokitDevice);
      98          18 :     return d->controller;
      99             : }
     100             : 
     101             : /// \cond
     102             : #define QTPOKIT_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          72 : CalibrationService * PokitDevice::calibration()
     119             : {
     120         184 :     QTPOKIT_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          72 : DataLoggerService * PokitDevice::dataLogger()
     131             : {
     132         184 :     QTPOKIT_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         384 : DeviceInfoService * PokitDevice::deviceInformation()
     144             : {
     145         988 :     QTPOKIT_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          72 : DsoService * PokitDevice::dso()
     156             : {
     157         184 :     QTPOKIT_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          72 : GenericAccessService * PokitDevice::genericAccess()
     168             : {
     169         184 :     QTPOKIT_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          72 : MultimeterService * PokitDevice::multimeter()
     180             : {
     181         184 :     QTPOKIT_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         252 : StatusService * PokitDevice::status()
     192             : {
     193         644 :     QTPOKIT_INTERNAL_GET_SERVICE(StatusService, status);
     194             : }
     195             : #undef QTPOKIT_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         144 : QString PokitDevice::serviceToString(const QBluetoothUuid &uuid)
     204             : {
     205             :     static QHash<QBluetoothUuid, QString> hash{
     206          24 :         { CalibrationService::serviceUuid, tr("Calibration") },
     207          18 :         { DataLoggerService::serviceUuid,  tr("Data Logger") },
     208          18 :         { DsoService::serviceUuid,         tr("DSO") },
     209          18 :         { MultimeterService::serviceUuid,  tr("Multimeter") },
     210          18 :         { StatusService::ServiceUuids::pokitMeter, tr("Status (Pokit Meter)") },
     211          18 :         { StatusService::ServiceUuids::pokitPro,   tr("Status (Pokit Pro)") },
     212             :         { DeviceInfoService::serviceUuid,
     213          36 :             QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid::DeviceInformation) },
     214             :         { GenericAccessService::serviceUuid,
     215          36 :             QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid::GenericAccess) },
     216         312 :     };
     217         152 :     return hash.value(uuid);
     218           0 : }
     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         360 : QString PokitDevice::charcteristicToString(const QBluetoothUuid &uuid)
     227             : {
     228             :     static QHash<QBluetoothUuid, QString> hash{
     229          24 :         { CalibrationService::CharacteristicUuids::temperature, tr("Temperature") },
     230             : 
     231          18 :         { DataLoggerService::CharacteristicUuids::metadata, tr("Metadata") },
     232          18 :         { DataLoggerService::CharacteristicUuids::reading,  tr("Reading") },
     233          18 :         { DataLoggerService::CharacteristicUuids::settings, tr("Settings") },
     234             : 
     235          18 :         { DsoService::CharacteristicUuids::metadata, tr("Metadata") },
     236          18 :         { DsoService::CharacteristicUuids::reading,  tr("Reading") },
     237          18 :         { DsoService::CharacteristicUuids::settings, tr("Settings") },
     238             : 
     239          18 :         { MultimeterService::CharacteristicUuids::reading,  tr("Reading") },
     240          18 :         { MultimeterService::CharacteristicUuids::settings, tr("Settings") },
     241             : 
     242          18 :         { StatusService::CharacteristicUuids::deviceCharacteristics, tr("Device Characteristics") },
     243          18 :         { StatusService::CharacteristicUuids::flashLed,              tr("Flash LED") },
     244          18 :         { StatusService::CharacteristicUuids::name,                  tr("Name") },
     245          18 :         { StatusService::CharacteristicUuids::status,                tr("Status") },
     246             : 
     247             :         { DeviceInfoService::CharacteristicUuids::firmwareRevision,
     248          36 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::FirmwareRevisionString) },
     249             :         { DeviceInfoService::CharacteristicUuids::hardwareRevision,
     250          36 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::HardwareRevisionString) },
     251             :         { DeviceInfoService::CharacteristicUuids::manufacturerName,
     252          36 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::ManufacturerNameString) },
     253             :         { DeviceInfoService::CharacteristicUuids::modelNumber,
     254          36 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::ModelNumberString) },
     255             :         { DeviceInfoService::CharacteristicUuids::softwareRevision,
     256          36 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::SoftwareRevisionString) },
     257             : 
     258             :         { GenericAccessService::CharacteristicUuids::appearance,
     259          36 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::Appearance) },
     260             :         { GenericAccessService::CharacteristicUuids::deviceName,
     261          36 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::DeviceName) },
     262         744 :     };
     263         380 :     return hash.value(uuid);
     264           0 : }
     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         816 : PokitDevicePrivate::PokitDevicePrivate(PokitDevice * const q) : q_ptr(q)
     277             : {
     278             : 
     279         816 : }
     280             : 
     281             : /*!
     282             :  * Sets \a newController to be used for accessing Pokit devices.
     283             :  *
     284             :  * If a controller has already been set (and is not the same pointer), then the previous controller
     285             :  * will be disconnected, and replaced with \a newController.
     286             :  *
     287             :  * This function will not take ownership of the new controller. The caller is responsible for
     288             :  * ensuring that \a newContorller remains valid for the lifetime of this instance, or until this
     289             :  * function is used again to replace \a newController with another one (which may be a nullptr).
     290             :  *
     291             :  * \see controller
     292             :  * \see PokitDevice::controller()
     293             :  */
     294         942 : void PokitDevicePrivate::setController(QLowEnergyController * newController)
     295             : {
     296         942 :     if (newController == this->controller) {
     297         360 :         qCDebug(lc).noquote() << tr("Controller already set to:") << newController;
     298         360 :         return;
     299             :     }
     300             : 
     301         582 :     if (this->controller) {
     302          54 :         qCDebug(lc).noquote() << tr("Disconnecting signals from previous controller:")
     303           0 :             << controller;
     304          54 :         disconnect(this->controller, nullptr, this, nullptr);
     305             :     }
     306             : 
     307         582 :     qCDebug(lc).noquote() << tr("Setting new controller:") << newController;
     308         582 :     this->controller = newController;
     309         582 :     if (!newController) {
     310             :         return; // Don't bother continuing to connect if new controller is null.
     311             :     }
     312             : 
     313         546 :     qCDebug(lc).noquote() << tr("Set new controller \"%1\" (%2) at (%3).").arg(
     314           0 :         controller->remoteName(), controller->remoteDeviceUuid().toString(),
     315           0 :         controller->remoteAddress().toString());
     316             : 
     317         546 :     connect(controller, &QLowEnergyController::connected,
     318             :             this, &PokitDevicePrivate::connected);
     319             : 
     320         546 :     connect(controller, &QLowEnergyController::connectionUpdated,
     321             :             this, &PokitDevicePrivate::connectionUpdated);
     322             : 
     323         546 :     connect(controller, &QLowEnergyController::disconnected,
     324             :             this, &PokitDevicePrivate::disconnected);
     325             : 
     326         546 :     connect(controller, &QLowEnergyController::discoveryFinished,
     327             :             this, &PokitDevicePrivate::discoveryFinished);
     328             : 
     329             : 
     330         546 :     connect(controller,
     331             :     #if (QT_VERSION < QT_VERSION_CHECK(6, 2, 0))
     332             :         QOverload<QLowEnergyController::Error>::of(&QLowEnergyController::error),
     333             :     #else
     334             :         &QLowEnergyController::errorOccurred,
     335             :     #endif
     336             :         this, &PokitDevicePrivate::errorOccurred);
     337             : 
     338             : 
     339         546 :     connect(controller, &QLowEnergyController::serviceDiscovered,
     340             :             this, &PokitDevicePrivate::serviceDiscovered);
     341             : 
     342         546 :     connect(controller, &QLowEnergyController::stateChanged,
     343             :             this, &PokitDevicePrivate::stateChanged);
     344             : }
     345             : 
     346             : /*!
     347             :  * Handle connected signals.
     348             :  */
     349          54 : void PokitDevicePrivate::connected() const
     350             : {
     351          54 :     if (controller == nullptr) {
     352          80 :         qCCritical(lc).noquote() << tr("PokitDevicePrivate::connected slot invoked without a controller.");
     353          36 :         return; // Just to avoid the nullptr dereference below.
     354             :     }
     355          18 :     qCDebug(lc).noquote() << tr("Connected to \"%1\" (%2) at (%3).").arg(
     356           0 :         controller->remoteName(), controller->remoteDeviceUuid().toString(),
     357           0 :         controller->remoteAddress().toString());
     358             : }
     359             : 
     360             : /*!
     361             :  * Handle connectionUpdated signals.
     362             :  */
     363          18 : void PokitDevicePrivate::connectionUpdated(const QLowEnergyConnectionParameters &newParameters) const
     364             : {
     365          18 :     qCDebug(lc).noquote() << tr("Connection updated:") << newParameters.latency()
     366           0 :         << newParameters.minimumInterval() << newParameters.maximumInterval()
     367           0 :         << newParameters.supervisionTimeout();
     368          18 : }
     369             : 
     370             : /*!
     371             :  * Handle disconnected signals.
     372             :  */
     373          18 : void PokitDevicePrivate::disconnected() const
     374             : {
     375          18 :     qCDebug(lc).noquote() << tr("Device disconnected.");
     376          18 : }
     377             : 
     378             : /*!
     379             :  * Handle discoveryFinished signals.
     380             :  */
     381          18 : void PokitDevicePrivate::discoveryFinished() const
     382             : {
     383          18 :     qCDebug(lc).noquote() << tr("Service discovery finished.");
     384          18 : }
     385             : 
     386             : /*!
     387             :  * Handle error signals.
     388             :  */
     389          18 : void PokitDevicePrivate::errorOccurred(QLowEnergyController::Error newError) const
     390             : {
     391          18 :     qCDebug(lc).noquote() << tr("Controller error:") << newError;
     392          18 : }
     393             : 
     394             : /*!
     395             :  * Handle serviceDiscovered signals.
     396             :  */
     397          18 : void PokitDevicePrivate::serviceDiscovered(const QBluetoothUuid &newService) const
     398             : {
     399          18 :     qCDebug(lc).noquote() << tr("Service discovered: %1 \"%2\"")
     400           0 :         .arg(newService.toString(), PokitDevice::serviceToString(newService));
     401          18 : }
     402             : 
     403             : /*!
     404             :  * Handle stateChanged signals.
     405             :  */
     406          18 : void PokitDevicePrivate::stateChanged(QLowEnergyController::ControllerState state) const
     407             : {
     408          18 :     qCDebug(lc).noquote() << tr("State changed to:") << state;
     409          18 : }
     410             : 
     411             : /// \endcond

Generated by: LCOV version 1.14