LCOV - code coverage report
Current view: top level - src/lib - pokitdevice.cpp (source / functions) Hit Total Coverage
Project: Dokit Lines: 115 128 89.8 %
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         180 : 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             :         // The next two are not specifically supported by this library, but strings provided for nicer debug output.
     217             :         { QBluetoothUuid::ServiceClassUuid::GenericAttribute,
     218          36 :             QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid::GenericAttribute) },
     219          36 :         { QBluetoothUuid(QStringLiteral("1d14d6ee-fd63-4fa1-bfa4-8f47b42119f0")), tr("OTA Firmware Update") },
     220         384 :     };
     221         190 :     return hash.value(uuid);
     222           0 : }
     223             : 
     224             : /*!
     225             :  * Returns a human-readable name for the \a uuid characteristic, or a null QString if unknown.
     226             :  *
     227             :  * This is equivalent to QBluetoothUuid::characteristicToString() but for characteristics provided
     228             :  * by Pokit devices.
     229             :  */
     230         432 : QString PokitDevice::charcteristicToString(const QBluetoothUuid &uuid)
     231             : {
     232             :     static QHash<QBluetoothUuid, QString> hash{
     233          24 :         { CalibrationService::CharacteristicUuids::temperature, tr("Temperature") },
     234          18 :         { CalibrationService::CharacteristicUuids::getParam,    tr("Get Param") },
     235          18 :         { CalibrationService::CharacteristicUuids::setParam,    tr("Set Param") },
     236             : 
     237          18 :         { DataLoggerService::CharacteristicUuids::metadata, tr("Metadata") },
     238          18 :         { DataLoggerService::CharacteristicUuids::reading,  tr("Reading") },
     239          18 :         { DataLoggerService::CharacteristicUuids::settings, tr("Settings") },
     240             : 
     241          18 :         { DsoService::CharacteristicUuids::metadata, tr("Metadata") },
     242          18 :         { DsoService::CharacteristicUuids::reading,  tr("Reading") },
     243          18 :         { DsoService::CharacteristicUuids::settings, tr("Settings") },
     244             : 
     245          18 :         { MultimeterService::CharacteristicUuids::reading,  tr("Reading") },
     246          18 :         { MultimeterService::CharacteristicUuids::settings, tr("Settings") },
     247             : 
     248          18 :         { StatusService::CharacteristicUuids::deviceCharacteristics, tr("Device Characteristics") },
     249          18 :         { StatusService::CharacteristicUuids::flashLed,              tr("Flash LED") },
     250          18 :         { StatusService::CharacteristicUuids::name,                  tr("Name") },
     251          18 :         { StatusService::CharacteristicUuids::status,                tr("Status") },
     252          18 :         { StatusService::CharacteristicUuids::torch,                 tr("Torch") },
     253          18 :         { StatusService::CharacteristicUuids::buttonPress,           tr("Button Press") },
     254             : 
     255             :         { DeviceInfoService::CharacteristicUuids::firmwareRevision,
     256          36 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::FirmwareRevisionString) },
     257             :         { DeviceInfoService::CharacteristicUuids::hardwareRevision,
     258          36 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::HardwareRevisionString) },
     259             :         { DeviceInfoService::CharacteristicUuids::manufacturerName,
     260          36 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::ManufacturerNameString) },
     261             :         { DeviceInfoService::CharacteristicUuids::modelNumber,
     262          34 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::ModelNumberString) },
     263             :         { DeviceInfoService::CharacteristicUuids::softwareRevision,
     264          36 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::SoftwareRevisionString) },
     265             :         { DeviceInfoService::CharacteristicUuids::serialNumber,
     266          36 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::SerialNumberString) },
     267             : 
     268             :         { GenericAccessService::CharacteristicUuids::appearance,
     269          36 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::Appearance) },
     270             :         { GenericAccessService::CharacteristicUuids::deviceName,
     271          36 :             QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::DeviceName) },
     272             : 
     273             :         // The next two are not specifically supported by this library, but strings provided for nicer debug output.
     274          36 :         { QBluetoothUuid(QStringLiteral("f7bf3564-fb6d-4e53-88a4-5e37e0326063")), tr("OTA Control") },
     275          36 :         { QBluetoothUuid(QStringLiteral("984227f3-34fc-4045-a5d0-2c581f81a153")), tr("OTA Data Transfer") },
     276         942 :     };
     277         456 :     return hash.value(uuid);
     278           0 : }
     279             : 
     280             : /*!
     281             :  * \cond internal
     282             :  * \class PokitDevicePrivate
     283             :  *
     284             :  * The PokitDevicePrivate class provides private implementation for PokitDevice.
     285             :  */
     286             : 
     287             : /*!
     288             :  * Constructs a new PokitDevicePrivate object with public implementation \a q.
     289             :  */
     290         816 : PokitDevicePrivate::PokitDevicePrivate(PokitDevice * const q) : q_ptr(q)
     291             : {
     292             : 
     293         816 : }
     294             : 
     295             : /*!
     296             :  * Sets \a newController to be used for accessing Pokit devices.
     297             :  *
     298             :  * If a controller has already been set (and is not the same pointer), then the previous controller
     299             :  * will be disconnected, and replaced with \a newController.
     300             :  *
     301             :  * This function will not take ownership of the new controller. The caller is responsible for
     302             :  * ensuring that \a newContorller remains valid for the lifetime of this instance, or until this
     303             :  * function is used again to replace \a newController with another one (which may be a nullptr).
     304             :  *
     305             :  * \see controller
     306             :  * \see PokitDevice::controller()
     307             :  */
     308         942 : void PokitDevicePrivate::setController(QLowEnergyController * newController)
     309             : {
     310         942 :     if (newController == this->controller) {
     311         360 :         qCDebug(lc).noquote() << tr("Controller already set to:") << newController;
     312         360 :         return;
     313             :     }
     314             : 
     315         582 :     if (this->controller) {
     316          54 :         qCDebug(lc).noquote() << tr("Disconnecting signals from previous controller:")
     317           0 :             << controller;
     318          54 :         disconnect(this->controller, nullptr, this, nullptr);
     319             :     }
     320             : 
     321         582 :     qCDebug(lc).noquote() << tr("Setting new controller:") << newController;
     322         582 :     this->controller = newController;
     323         582 :     if (!newController) {
     324             :         return; // Don't bother continuing to connect if new controller is null.
     325             :     }
     326             : 
     327         546 :     qCDebug(lc).noquote() << tr(R"(Set new controller "%1" (%2) at (%3).)").arg(
     328           0 :         controller->remoteName(), controller->remoteDeviceUuid().toString(),
     329           0 :         controller->remoteAddress().toString());
     330             : 
     331         546 :     connect(controller, &QLowEnergyController::connected,
     332             :             this, &PokitDevicePrivate::connected);
     333             : 
     334         546 :     connect(controller, &QLowEnergyController::connectionUpdated,
     335             :             this, &PokitDevicePrivate::connectionUpdated);
     336             : 
     337         546 :     connect(controller, &QLowEnergyController::disconnected,
     338             :             this, &PokitDevicePrivate::disconnected);
     339             : 
     340         546 :     connect(controller, &QLowEnergyController::discoveryFinished,
     341             :             this, &PokitDevicePrivate::discoveryFinished);
     342             : 
     343             : 
     344         546 :     connect(controller,
     345             :     #if (QT_VERSION < QT_VERSION_CHECK(6, 2, 0))
     346             :         QOverload<QLowEnergyController::Error>::of(&QLowEnergyController::error),
     347             :     #else
     348             :         &QLowEnergyController::errorOccurred,
     349             :     #endif
     350             :         this, &PokitDevicePrivate::errorOccurred);
     351             : 
     352             : 
     353         546 :     connect(controller, &QLowEnergyController::serviceDiscovered,
     354             :             this, &PokitDevicePrivate::serviceDiscovered);
     355             : 
     356         546 :     connect(controller, &QLowEnergyController::stateChanged,
     357             :             this, &PokitDevicePrivate::stateChanged);
     358             : }
     359             : 
     360             : /*!
     361             :  * Handle connected signals.
     362             :  */
     363          54 : void PokitDevicePrivate::connected() const
     364             : {
     365          54 :     if (controller == nullptr) {
     366          80 :         qCCritical(lc).noquote() << tr("PokitDevicePrivate::connected slot invoked without a controller.");
     367          36 :         return; // Just to avoid the nullptr dereference below.
     368             :     }
     369          18 :     qCDebug(lc).noquote() << tr(R"(Connected to "%1" (%2) at (%3).)").arg(
     370           0 :         controller->remoteName(), controller->remoteDeviceUuid().toString(),
     371           0 :         controller->remoteAddress().toString());
     372             : }
     373             : 
     374             : /*!
     375             :  * Handle connectionUpdated signals.
     376             :  */
     377          18 : void PokitDevicePrivate::connectionUpdated(const QLowEnergyConnectionParameters &newParameters) const
     378             : {
     379          18 :     qCDebug(lc).noquote() << tr("Connection updated:") << newParameters.latency()
     380           0 :         << newParameters.minimumInterval() << newParameters.maximumInterval()
     381           0 :         << newParameters.supervisionTimeout();
     382          18 : }
     383             : 
     384             : /*!
     385             :  * Handle disconnected signals.
     386             :  */
     387          18 : void PokitDevicePrivate::disconnected() const
     388             : {
     389          18 :     qCDebug(lc).noquote() << tr("Device disconnected.");
     390          18 : }
     391             : 
     392             : /*!
     393             :  * Handle discoveryFinished signals.
     394             :  */
     395          18 : void PokitDevicePrivate::discoveryFinished() const
     396             : {
     397          18 :     qCDebug(lc).noquote() << tr("Service discovery finished.");
     398          18 : }
     399             : 
     400             : /*!
     401             :  * Handle error signals.
     402             :  */
     403          18 : void PokitDevicePrivate::errorOccurred(QLowEnergyController::Error newError) const
     404             : {
     405          18 :     qCDebug(lc).noquote() << tr("Controller error:") << newError;
     406          18 : }
     407             : 
     408             : /*!
     409             :  * Handle serviceDiscovered signals.
     410             :  */
     411          18 : void PokitDevicePrivate::serviceDiscovered(const QBluetoothUuid &newService) const
     412             : {
     413          18 :     qCDebug(lc).noquote() << tr(R"(Service discovered: %1 "%2")")
     414           0 :         .arg(newService.toString(), PokitDevice::serviceToString(newService));
     415          18 : }
     416             : 
     417             : /*!
     418             :  * Handle stateChanged signals.
     419             :  */
     420          18 : void PokitDevicePrivate::stateChanged(QLowEnergyController::ControllerState state) const
     421             : {
     422          18 :     qCDebug(lc).noquote() << tr("State changed to:") << state;
     423          18 : }
     424             : 
     425             : /// \endcond

Generated by: LCOV version 1.14