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

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

Generated by: LCOV version 2.4-0