LCOV - code coverage report
Current view: top level - src/lib - dataloggerservice.cpp (source / functions) Hit Total Coverage
Project: Dokit Lines: 186 219 84.9 %
Version: Functions: 42 46 91.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 DataLoggerService and DataLoggerServicePrivate classes.
       7             :  */
       8             : 
       9             : #include <qtpokit/dataloggerservice.h>
      10             : #include "dataloggerservice_p.h"
      11             : 
      12             : #include <qtpokit/statusservice.h>
      13             : 
      14             : #include <QDataStream>
      15             : #include <QIODevice>
      16             : #include <QLowEnergyController>
      17             : #include <QtEndian>
      18             : 
      19             : /*!
      20             :  * \class DataLoggerService
      21             :  *
      22             :  * The DataLoggerService class accesses the `Data Logger` service of Pokit devices.
      23             :  */
      24             : 
      25             : /// UUID of the "DataLogger" service.
      26             : const QBluetoothUuid DataLoggerService::
      27             :     serviceUuid(QLatin1String("a5ff3566-1fd8-4e10-8362-590a578a4121"));
      28             : 
      29             : /// \struct DataLoggerService::CharacteristicUuids
      30             : /// \brief Characteristics available via the `DataLogger` service.
      31             : 
      32             : /// UUID of the `DataLogger` service's `Settings` characterstic.
      33             : const QBluetoothUuid DataLoggerService::CharacteristicUuids::
      34             :     settings(QLatin1String("5f97c62b-a83b-46c6-b9cd-cac59e130a78"));
      35             : 
      36             : /// UUID of the `DataLogger` service's `Metadata` characterstic.
      37             : const QBluetoothUuid DataLoggerService::CharacteristicUuids::
      38             :     metadata(QLatin1String("9acada2e-3936-430b-a8f7-da407d97ca6e"));
      39             : 
      40             : /// UUID of the `DataLogger` service's `Reading` characterstic.
      41             : const QBluetoothUuid DataLoggerService::CharacteristicUuids::
      42             :     reading(QLatin1String("3c669dab-fc86-411c-9498-4f9415049cc0"));
      43             : 
      44             : /// \enum DataLoggerService::Command
      45             : /// \brief Values supported by the `Command` attribute of the `Settings` characteristic.
      46             : 
      47             : /// \enum DataLoggerService::Mode
      48             : /// \brief Values supported by the `Mode` attribute of the `Settings` and `Metadata` characteristics.
      49             : 
      50             : /// Returns \a mode as a user-friendly string.
      51        2754 : QString DataLoggerService::toString(const Mode &mode)
      52             : {
      53        2754 :     switch (mode) {
      54          18 :     case Mode::Idle:        return tr("Idle");
      55         540 :     case Mode::DcVoltage:   return tr("DC voltage");
      56         540 :     case Mode::AcVoltage:   return tr("AC voltage");
      57         540 :     case Mode::DcCurrent:   return tr("DC current");
      58         540 :     case Mode::AcCurrent:   return tr("AC current");
      59         540 :     case Mode::Temperature: return tr("Temperature");
      60             :     default:                return QString();
      61             :     }
      62             : }
      63             : 
      64             : /// \enum DataLoggerService::VoltageRange
      65             : /// \brief Values supported by the `Range` attribute of the `Settings` and `Metadata` characteristics,
      66             : /// when `Mode` is AC or DC voltage.
      67             : 
      68             : /// Returns \a range as a user-friendly string.
      69         864 : QString DataLoggerService::toString(const VoltageRange &range)
      70             : {
      71         864 :     switch (range) {
      72          54 :     case VoltageRange::_0_to_300mV:  return tr("0 to 300mV");
      73         666 :     case VoltageRange::_300mV_to_2V: return tr("300mV to 2V");
      74          18 :     case VoltageRange::_2V_to_6V:    return tr("2V to 6V");
      75          18 :     case VoltageRange::_6V_to_12V:   return tr("6V to 12V");
      76          18 :     case VoltageRange::_12V_to_30V:  return tr("12V to 30V");
      77          54 :     case VoltageRange::_30V_to_60V:  return tr("30V to 60V");
      78             :     default:                         return QString();
      79             :     }
      80             : }
      81             : 
      82             : /*!
      83             :  *  Returns the minimum value for \a range in (integer) millivolts, or null QVariant if \a range is
      84             :  *  not valid.
      85             :  *
      86             :  *  Note, this is an *absolute* minimum. That is, the true range for DC measurements is from
      87             :  *  `-maxValue(range)` to `+maxValue(range)`. In this sense, `minValue(range)` indicates the
      88             :  *  magnitude (ignore signs) that can be measured accurately for the given \a range. As AC voltage
      89             :  *  can never be negative, this is relevant for DC voltage only.
      90             :  */
      91         144 : QVariant DataLoggerService::minValue(const VoltageRange &range)
      92             : {
      93         144 :     switch (range) {
      94          18 :     case VoltageRange::_0_to_300mV:  return     0;
      95          18 :     case VoltageRange::_300mV_to_2V: return   300;
      96          18 :     case VoltageRange::_2V_to_6V:    return  2000;
      97          18 :     case VoltageRange::_6V_to_12V:   return  6000;
      98          18 :     case VoltageRange::_12V_to_30V:  return 12000;
      99          18 :     case VoltageRange::_30V_to_60V:  return 30000;
     100             :     default:                         return QVariant();
     101             :     }
     102             : }
     103             : 
     104             : /*!
     105             :  *  Returns the maximum value for \a range in (integer) millivolts, or null QVariant if \a range is
     106             :  *  not valid.
     107             :  */
     108        3672 : QVariant DataLoggerService::maxValue(const VoltageRange &range)
     109             : {
     110        3672 :     switch (range) {
     111        1152 :     case VoltageRange::_0_to_300mV:  return   300;
     112         990 :     case VoltageRange::_300mV_to_2V: return  2000;
     113         630 :     case VoltageRange::_2V_to_6V:    return  6000;
     114         450 :     case VoltageRange::_6V_to_12V:   return 12000;
     115         288 :     case VoltageRange::_12V_to_30V:  return 30000;
     116         126 :     case VoltageRange::_30V_to_60V:  return 60000;
     117             :     default:                         return QVariant();
     118             :     }
     119             : }
     120             : 
     121             : /// \enum DataLoggerService::CurrentRange
     122             : /// \brief Values supported by the `Range` attribute of the `Settings` and `Metadata` characteristics,
     123             : /// when `Mode` is AC or DC current.
     124             : 
     125             : /// Returns \a range as a user-friendly string.
     126         846 : QString DataLoggerService::toString(const CurrentRange &range)
     127             : {
     128         846 :     switch (range) {
     129          54 :     case CurrentRange::_0_to_10mA:      return tr("0 to 10mA");
     130          18 :     case CurrentRange::_10mA_to_30mA:   return tr("10mA to 30mA");
     131         342 :     case CurrentRange::_30mA_to_150mA:  return tr("30mA to 150mA");
     132          18 :     case CurrentRange::_150mA_to_300mA: return tr("150mA to 300mA");
     133         378 :     case CurrentRange::_300mA_to_3A:    return tr("300mA to 3A");
     134             :     default:                            return QString();
     135             :     }
     136             : }
     137             : 
     138             : /*!
     139             :  *  Returns the minimum value for \a range in (integer) milliamps, or null QVariant if \a range is
     140             :  *  not valid.
     141             :  *
     142             :  *  Note, this is an *absolute* minimum. That is, the true range for DC measurements is from
     143             :  *  `-maxValue(range)` to `+maxValue(range)`. In this sense, `minValue(range)` indicates the
     144             :  *  magnitude (ignore signs) that can be measured accurately for the given \a range. As AC current
     145             :  *  can never be negative, this is relevant for DC current only.
     146             :  */
     147         126 : QVariant DataLoggerService::minValue(const CurrentRange &range)
     148             : {
     149         126 :     switch (range) {
     150          18 :     case CurrentRange::_0_to_10mA:      return   0;
     151          18 :     case CurrentRange::_10mA_to_30mA:   return  10;
     152          18 :     case CurrentRange::_30mA_to_150mA:  return  30;
     153          18 :     case CurrentRange::_150mA_to_300mA: return 150;
     154          18 :     case CurrentRange::_300mA_to_3A:    return 300;
     155             :     default:                            return QVariant();
     156             :     }
     157             : }
     158             : 
     159             : /*!
     160             :  *  Returns the maximum value for \a range in (integer) milliamps, or null QVariant if \a range is
     161             :  *  not valid.
     162             :  */
     163        2430 : QVariant DataLoggerService::maxValue(const CurrentRange &range)
     164             : {
     165        2430 :     switch (range) {
     166         810 :     case CurrentRange::_0_to_10mA:      return   10;
     167         648 :     case CurrentRange::_10mA_to_30mA:   return   30;
     168         486 :     case CurrentRange::_30mA_to_150mA:  return  150;
     169         306 :     case CurrentRange::_150mA_to_300mA: return  300;
     170         144 :     case CurrentRange::_300mA_to_3A:    return 3000;
     171             :     default:                            return QVariant();
     172             :     }
     173             : }
     174             : 
     175             : /// \union DataLoggerService::Range
     176             : /// \brief Values supported by the `Range` attribute of the `Settings` and `Metadata` characteristics.
     177             : 
     178             : static_assert(std::is_same<std::underlying_type_t<DataLoggerService::VoltageRange>,
     179             :                            std::underlying_type_t<DataLoggerService::CurrentRange>>::value,
     180             :               "DataLoggerService::Range members must all have the same underlying type.");
     181             : 
     182             : /// Constructs a new DataLoggerService::Range instance with 0. This should be considered
     183        1440 : DataLoggerService::Range::Range() : voltageRange(static_cast<DataLoggerService::VoltageRange>(0))
     184             : {
     185             : 
     186        1440 : }
     187             : 
     188             : /// Constructs a new DataLoggerService::Range instance with \a range.
     189        3078 : DataLoggerService::Range::Range(const DataLoggerService::VoltageRange range) : voltageRange(range)
     190             : {
     191             : 
     192        3030 : }
     193             : 
     194             : /// Constructs a new DataLoggerService::Range instance with \a range.
     195        1224 : DataLoggerService::Range::Range(const DataLoggerService::CurrentRange range) : currentRange(range)
     196             : {
     197             : 
     198        1224 : }
     199             : 
     200             : /// Returns \a range as a user-friendly string, or a null QString if \a mode has no ranges.
     201        1782 : QString DataLoggerService::toString(const Range &range, const Mode &mode)
     202             : {
     203        1782 :     switch (mode) {
     204         720 :     case Mode::DcVoltage:
     205             :     case Mode::AcVoltage:
     206         720 :         return toString(range.voltageRange);
     207         720 :     case Mode::DcCurrent:
     208             :     case Mode::AcCurrent:
     209         720 :         return toString(range.currentRange);
     210             :     default:
     211             :         return QString();
     212             :     }
     213             : }
     214             : 
     215             : /// Returns \c true if \a lhs is numerically equal to \a rhs, \c false otherwise.
     216        1530 : bool operator==(const DataLoggerService::Range &lhs, const DataLoggerService::Range &rhs)
     217             : {
     218        1530 :     return static_cast<std::underlying_type_t<DataLoggerService::VoltageRange>>(lhs.voltageRange)
     219        1530 :         == static_cast<std::underlying_type_t<DataLoggerService::VoltageRange>>(rhs.voltageRange);
     220             : }
     221             : 
     222             : /// Returns \c true if \a lhs is numerically not-equal to \a rhs, \c false otherwise.
     223          18 : bool operator!=(const DataLoggerService::Range &lhs, const DataLoggerService::Range &rhs)
     224             : {
     225          18 :     return static_cast<std::underlying_type_t<DataLoggerService::VoltageRange>>(lhs.voltageRange)
     226          18 :         != static_cast<std::underlying_type_t<DataLoggerService::VoltageRange>>(rhs.voltageRange);
     227             : }
     228             : 
     229             : /// Returns \c true if \a lhs is numerically less than \a rhs, \c false otherwise.
     230          18 : bool operator< (const DataLoggerService::Range &lhs, const DataLoggerService::Range &rhs)
     231             : {
     232          18 :     return static_cast<std::underlying_type_t<DataLoggerService::VoltageRange>>(lhs.voltageRange)
     233          18 :          < static_cast<std::underlying_type_t<DataLoggerService::VoltageRange>>(rhs.voltageRange);
     234             : }
     235             : 
     236             : /// Returns \c true if \a lhs is numerically greater than \a rhs, \c false otherwise.
     237          18 : bool operator> (const DataLoggerService::Range &lhs, const DataLoggerService::Range &rhs)
     238             : {
     239          18 :     return static_cast<std::underlying_type_t<DataLoggerService::VoltageRange>>(lhs.voltageRange)
     240          18 :          > static_cast<std::underlying_type_t<DataLoggerService::VoltageRange>>(rhs.voltageRange);
     241             : }
     242             : 
     243             : /// Returns \c true if \a lhs is numerically less than or equal to \a rhs, \c false otherwise.
     244          36 : bool operator<=(const DataLoggerService::Range &lhs, const DataLoggerService::Range &rhs)
     245             : {
     246          36 :     return static_cast<std::underlying_type_t<DataLoggerService::VoltageRange>>(lhs.voltageRange)
     247          36 :         <= static_cast<std::underlying_type_t<DataLoggerService::VoltageRange>>(rhs.voltageRange);
     248             : }
     249             : 
     250             : /// Returns \c true if \a lhs is numerically greater than or equal to \a rhs, \c false otherwise.
     251          36 : bool operator>=(const DataLoggerService::Range &lhs, const DataLoggerService::Range &rhs)
     252             : {
     253          36 :     return static_cast<std::underlying_type_t<DataLoggerService::VoltageRange>>(lhs.voltageRange)
     254          36 :         >= static_cast<std::underlying_type_t<DataLoggerService::VoltageRange>>(rhs.voltageRange);
     255             : }
     256             : 
     257             : /// \struct DataLoggerService::Settings
     258             : /// \brief Attributes included in the `Settings` characterstic.
     259             : 
     260             : /// \enum DataLoggerService::LoggerStatus
     261             : /// \brief Values supported by the `Status` attribute of the `Metadata` characteristic.
     262             : 
     263             : /// \struct DataLoggerService::Metadata
     264             : /// \brief Attributes included in the `Metadata` characterstic.
     265             : 
     266             : /*!
     267             :  * \typedef DataLoggerService::Samples
     268             :  *
     269             :  * Raw samples from the `Reading` characteristic. These raw samples are (supposedly) within the
     270             :  * range -2048 to +2047, and need to be multiplied by the Metadata::scale value from the `Metadata`
     271             :  * characteristc to get the true values.
     272             :  *
     273             :  * Also supposedly, there should be no more than 10 samples at a time, according to Pokit's current
     274             :  * API docs. There is not artificial limitation imposed by QtPokit, so devices may begin batching
     275             :  * more samples in future. Specifically, the Pokit Pro seems to send 88 samples (in 176 bytes) at a
     276             :  * time.
     277             :  */
     278             : 
     279             : /*!
     280             :  * Constructs a new Pokit service with \a parent.
     281             :  */
     282         288 : DataLoggerService::DataLoggerService(QLowEnergyController * const controller, QObject * parent)
     283         288 :     : AbstractPokitService(new DataLoggerServicePrivate(controller, this), parent)
     284             : {
     285             : 
     286         288 : }
     287             : 
     288             : /*!
     289             :  * \cond internal
     290             :  * Constructs a new Pokit service with \a parent, and private implementation \a d.
     291             :  */
     292           0 : DataLoggerService::DataLoggerService(
     293           0 :     DataLoggerServicePrivate * const d, QObject * const parent)
     294           0 :     : AbstractPokitService(d, parent)
     295             : {
     296             : 
     297           0 : }
     298             : /// \endcond
     299             : 
     300             : /*!
     301             :  * Destroys this DataLoggerService object.
     302             :  */
     303         270 : DataLoggerService::~DataLoggerService()
     304             : {
     305             : 
     306         270 : }
     307             : 
     308          18 : bool DataLoggerService::readCharacteristics()
     309             : {
     310          18 :     return readMetadataCharacteristic();
     311             : }
     312             : 
     313             : /*!
     314             :  * Reads the `DataLogger` service's `Metadata` characteristic.
     315             :  *
     316             :  * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
     317             :  * underlying controller it not yet connected to the Pokit device, or the device's services have
     318             :  * not yet been discovered).
     319             :  *
     320             :  * Emits metadataRead() if/when the characteristic has been read successfully.
     321             :  */
     322          30 : bool DataLoggerService::readMetadataCharacteristic()
     323             : {
     324             :     Q_D(DataLoggerService);
     325          36 :     return d->readCharacteristic(CharacteristicUuids::metadata);
     326             : }
     327             : 
     328             : /*!
     329             :  * Configures the Pokit device's data logger mode.
     330             :  *
     331             :  * Returns `true` if the write request was successfully queued, `false` otherwise.
     332             :  *
     333             :  * Emits settingsWritten() if/when the \a settings have been writtem successfully.
     334             :  */
     335          72 : bool DataLoggerService::setSettings(const Settings &settings)
     336             : {
     337             :     Q_D(const DataLoggerService);
     338             :     const QLowEnergyCharacteristic characteristic =
     339          72 :         d->getCharacteristic(CharacteristicUuids::settings);
     340          72 :     if (!characteristic.isValid()) {
     341             :         return false;
     342             :     }
     343             : 
     344             :     const bool updateIntervalIs32bit =
     345           0 :         (d->getCharacteristic(CharacteristicUuids::metadata).value().size() >= 23);
     346           0 :     const QByteArray value = DataLoggerServicePrivate::encodeSettings(settings, updateIntervalIs32bit);
     347           0 :     if (value.isNull()) {
     348             :         return false;
     349             :     }
     350             : 
     351           0 :     d->service->writeCharacteristic(characteristic, value);
     352           0 :     return (d->service->error() != QLowEnergyService::ServiceError::CharacteristicWriteError);
     353          72 : }
     354             : 
     355             : /*!
     356             :  * Start the data logger with \a settings.
     357             :  *
     358             :  * This is just a synonym for setSettings() except makes the caller's intention more explicit, and
     359             :  * sanity-checks that the settings's command is DataLoggerService::Command::Start.
     360             :  */
     361          54 : bool DataLoggerService::startLogger(const Settings &settings)
     362             : {
     363             :     Q_D(const DataLoggerService);
     364             :     Q_ASSERT(settings.command == DataLoggerService::Command::Start);
     365          54 :     if (settings.command != DataLoggerService::Command::Start) {
     366          80 :         qCWarning(d->lc).noquote() << tr("Settings command must be 'Start'.");
     367          36 :         return false;
     368             :     }
     369          18 :     return setSettings(settings);
     370             : }
     371             : 
     372             : /*!
     373             :  * Stop the data logger.
     374             :  *
     375             :  * This is just a convenience function equivalent to calling setSettings() with the command set to
     376             :  * DataLoggerService::Command::Stop.
     377             :  */
     378          18 : bool DataLoggerService::stopLogger()
     379             : {
     380             :     // Note, only the Settings::command member need be set, since the others are all ignored by the
     381             :     // Pokit device when the command is Stop. However, we still explicitly initialise all other
     382             :     // members just to ensure we're never exposing uninitialised RAM to an external device.
     383          18 :     return setSettings({
     384             :         DataLoggerService::Command::Stop,
     385             :         0, DataLoggerService::Mode::Idle,
     386             :         DataLoggerService::VoltageRange::_0_to_300mV, 0, 0
     387          18 :     });
     388             : }
     389             : 
     390             : /*!
     391             :  * Start the data logger.
     392             :  *
     393             :  * This is just a convenience function equivalent to calling setSettings() with the command set to
     394             :  * DataLoggerService::Command::Refresh.
     395             :  *
     396             :  * Once the Pokit device has processed this request succesffully, the device will begin notifying
     397             :  * the `Metadata` and `Reading` characteristic, resulting in emits of metadataRead and samplesRead
     398             :  * respectively.
     399             :  */
     400          18 : bool DataLoggerService::fetchSamples()
     401             : {
     402             :     // Note, only the Settings::command member need be set, since the others are all ignored by the
     403             :     // Pokit device when the command is Refresh. However, we still explicitly initialise all other
     404             :     // members just to ensure we're never exposing uninitialised RAM to an external device.
     405          18 :     return setSettings({
     406             :         DataLoggerService::Command::Refresh,
     407             :         0, DataLoggerService::Mode::Idle,
     408             :         DataLoggerService::VoltageRange::_0_to_300mV, 0, 0
     409          18 :     });
     410             : }
     411             : 
     412             : /*!
     413             :  * Returns the most recent value of the `DataLogger` service's `Metadata` characteristic.
     414             :  *
     415             :  * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
     416             :  * currently available (ie the serviceDetailsDiscovered signal has not been emitted yet), then the
     417             :  * returned DataLoggerService::Metadata::scale member will be a quiet NaN, which can be checked like:
     418             :  *
     419             :  * ```
     420             :  * const DataLoggerService::Metadata metadata = multimeterService->metadata();
     421             :  * if (qIsNaN(metadata.scale)) {
     422             :  *     // Handle failure.
     423             :  * }
     424             :  * ```
     425             :  */
     426          18 : DataLoggerService::Metadata DataLoggerService::metadata() const
     427             : {
     428             :     Q_D(const DataLoggerService);
     429             :     const QLowEnergyCharacteristic characteristic =
     430          18 :         d->getCharacteristic(CharacteristicUuids::metadata);
     431          18 :     return (characteristic.isValid()) ? DataLoggerServicePrivate::parseMetadata(characteristic.value())
     432             :         : Metadata{ LoggerStatus::Error, std::numeric_limits<float>::quiet_NaN(),
     433          36 :                     Mode::Idle, VoltageRange::_0_to_300mV, 0, 0, 0 };
     434          18 : }
     435             : 
     436             : /*!
     437             :  * Enables client-side notifications of Data Logger metadata changes.
     438             :  *
     439             :  * This is an alternative to manually requesting individual reads via readMetadataCharacteristic().
     440             :  *
     441             :  * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
     442             :  *
     443             :  * Successfully read values (if any) will be emitted via the metadataRead() signal.
     444             :  */
     445          18 : bool DataLoggerService::enableMetadataNotifications()
     446             : {
     447             :     Q_D(DataLoggerService);
     448          18 :     return d->enableCharacteristicNotificatons(CharacteristicUuids::metadata);
     449             : }
     450             : 
     451             : /*!
     452             :  * Disables client-side notifications of Data Logger metadata changes.
     453             :  *
     454             :  * Instantaneous reads can still be fetched by readMetadataCharacteristic().
     455             :  *
     456             :  * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
     457             :  */
     458          18 : bool DataLoggerService::disableMetadataNotifications()
     459             : {
     460             :     Q_D(DataLoggerService);
     461          18 :     return d->disableCharacteristicNotificatons(CharacteristicUuids::metadata);
     462             : }
     463             : 
     464             : /*!
     465             :  * Enables client-side notifications of Data Logger readings.
     466             :  *
     467             :  * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
     468             :  *
     469             :  * Successfully read samples (if any) will be emitted via the samplesRead() signal.
     470             :  */
     471          18 : bool DataLoggerService::enableReadingNotifications()
     472             : {
     473             :     Q_D(DataLoggerService);
     474          18 :     return d->enableCharacteristicNotificatons(CharacteristicUuids::reading);
     475             : }
     476             : 
     477             : /*!
     478             :  * Disables client-side notifications of Data Logger readings.
     479             :  *
     480             :  * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
     481             :  */
     482          18 : bool DataLoggerService::disableReadingNotifications()
     483             : {
     484             :     Q_D(DataLoggerService);
     485          18 :     return d->disableCharacteristicNotificatons(CharacteristicUuids::reading);
     486             : }
     487             : 
     488             : /*!
     489             :  * \fn DataLoggerService::settingsWritten
     490             :  *
     491             :  * This signal is emitted when the `Settings` characteristic has been written successfully.
     492             :  *
     493             :  * \see setSettings
     494             :  */
     495             : 
     496             : /*!
     497             :  * \fn DataLoggerService::metadataRead
     498             :  *
     499             :  * This signal is emitted when the `Metadata` characteristic has been read successfully.
     500             :  *
     501             :  * \see readMetadataCharacteristic
     502             :  */
     503             : 
     504             : /*!
     505             :  * \fn DataLoggerService::samplesRead
     506             :  *
     507             :  * This signal is emitted when the `Reading` characteristic has been notified.
     508             :  *
     509             :  * \see beginSampling
     510             :  * \see stopSampling
     511             :  */
     512             : 
     513             : 
     514             : /*!
     515             :  * \cond internal
     516             :  * \class DataLoggerServicePrivate
     517             :  *
     518             :  * The DataLoggerServicePrivate class provides private implementation for DataLoggerService.
     519             :  */
     520             : 
     521             : /*!
     522             :  * \internal
     523             :  * Constructs a new DataLoggerServicePrivate object with public implementation \a q.
     524             :  */
     525         192 : DataLoggerServicePrivate::DataLoggerServicePrivate(
     526         288 :     QLowEnergyController * controller, DataLoggerService * const q)
     527         288 :     : AbstractPokitServicePrivate(DataLoggerService::serviceUuid, controller, q)
     528             : {
     529             : 
     530         192 : }
     531             : 
     532             : /*!
     533             :  * Returns \a settings in the format Pokit devices expect. If \a updateIntervalIs32bit is \c true
     534             :  * then the `Update Interval` field will be encoded in 32-bit instead of 16.
     535             :  */
     536         126 : QByteArray DataLoggerServicePrivate::encodeSettings(const DataLoggerService::Settings &settings,
     537             :                                                     const bool updateIntervalIs32bit)
     538             : {
     539             :     static_assert(sizeof(settings.command)        == 1, "Expected to be 1 byte.");
     540             :     static_assert(sizeof(settings.arguments)      == 2, "Expected to be 2 bytes.");
     541             :     static_assert(sizeof(settings.mode)           == 1, "Expected to be 1 byte.");
     542             :     static_assert(sizeof(settings.range)          == 1, "Expected to be 1 byte.");
     543             :     static_assert(sizeof(settings.updateInterval) == 4, "Expected to be 4 bytes.");
     544             :     static_assert(sizeof(settings.timestamp)      == 4, "Expected to be 4 bytes.");
     545             : 
     546          28 :     QByteArray value;
     547         126 :     QDataStream stream(&value, QIODevice::WriteOnly);
     548         126 :     stream.setByteOrder(QDataStream::LittleEndian);
     549         126 :     stream.setFloatingPointPrecision(QDataStream::SinglePrecision); // 32-bit floats, not 64-bit.
     550         126 :     stream << (quint8)settings.command << settings.arguments << (quint8)settings.mode
     551         126 :            << (quint8)settings.range.voltageRange;
     552             : 
     553             :     /*!
     554             :      * \pokitApi For Pokit Meter, `updateInterval` is `uint16` seconds (as per the Pokit API 1.00),
     555             :      * however for Pokit Pro it's `uint32` milliseconds, even though that's not officially
     556             :      * documented anywhere.
     557             :      */
     558             : 
     559         126 :     if (!updateIntervalIs32bit) {
     560          36 :         stream << (quint16)((settings.updateInterval+500)/1000) << settings.timestamp;
     561             :         Q_ASSERT(value.size() == 11); // According to Pokit API 1.00.
     562             :     } else {
     563          90 :         stream << settings.updateInterval << settings.timestamp;
     564             :         Q_ASSERT(value.size() == 13); // According to testing / experimentation.
     565             :     }
     566         126 :     return value;
     567         126 : }
     568             : 
     569             : /*!
     570             :  * Parses the `Metadata` \a value into a DataLoggerService::Metatdata struct.
     571             :  */
     572          90 : DataLoggerService::Metadata DataLoggerServicePrivate::parseMetadata(const QByteArray &value)
     573             : {
     574          90 :     DataLoggerService::Metadata metadata{
     575             :         DataLoggerService::LoggerStatus::Error, std::numeric_limits<float>::quiet_NaN(),
     576             :         DataLoggerService::Mode::Idle, DataLoggerService::VoltageRange::_0_to_300mV,
     577             :         0, 0, 0
     578          60 :     };
     579             : 
     580             :     // Pokit Meter: 15 bytes, Pokit Pro: 23 bytes.
     581         110 :     if (!checkSize(QLatin1String("Metadata"), value, 15, 23)) {
     582             :         return metadata;
     583             :     }
     584             : 
     585          54 :     qCDebug(lc) << value.mid(7,12).toHex(',');
     586          54 :     metadata.status             = static_cast<DataLoggerService::LoggerStatus>(value.at(0));
     587          66 :     metadata.scale              = qFromLittleEndian<float>(value.mid(1,4));
     588          54 :     metadata.mode               = static_cast<DataLoggerService::Mode>(value.at(5));
     589          54 :     metadata.range.voltageRange = static_cast<DataLoggerService::VoltageRange>(value.at(6));
     590             : 
     591             :     /*!
     592             :      * \pokitApi For Pokit Meter, `updateInterval` is `uint16` (as per the Pokit API 1.00), however
     593             :      * for Pokit Pro it's `uint32`, even though that's not officially documented anywhere.
     594             :      * Also note, the doc claims 'microseconds' (ie 10^-6), but clearly the value is 'milliseconds'
     595             :      * (ie 10^-3) for Pokit Pro, and whole seconds for Pokit Meter.
     596             :      */
     597             : 
     598          54 :     if (value.size() == 15) {
     599          22 :         metadata.updateInterval  = qFromLittleEndian<quint16>(value.mid(7,2))*1000;
     600          22 :         metadata.numberOfSamples = qFromLittleEndian<quint16>(value.mid(9,2));
     601          22 :         metadata.timestamp       = qFromLittleEndian<quint32>(value.mid(11,4));
     602          36 :     } else if (value.size() == 23) {
     603          22 :         metadata.updateInterval  = qFromLittleEndian<quint32>(value.mid(7,4));
     604          22 :         metadata.numberOfSamples = qFromLittleEndian<quint16>(value.mid(11,2));
     605          22 :         metadata.timestamp       = qFromLittleEndian<quint32>(value.mid(19,4));
     606             :     } else {
     607          36 :         qCWarning(lc).noquote() << tr("Cannot decode metadata of %n byte/s: %1", nullptr, value.size())
     608          22 :             .arg(toHexString(value));
     609             :     }
     610             :     return metadata;
     611             : }
     612             : 
     613             : /*!
     614             :  * Parses the `Reading` \a value into a DataLoggerService::Samples vector.
     615             :  */
     616          72 : DataLoggerService::Samples DataLoggerServicePrivate::parseSamples(const QByteArray &value)
     617             : {
     618          16 :     DataLoggerService::Samples samples;
     619          72 :     if ((value.size()%2) != 0) {
     620          42 :         qCWarning(lc).noquote() << tr("Samples value has odd size %1 (should be even): %2")
     621          28 :             .arg(value.size()).arg(toHexString(value));
     622           3 :         return samples;
     623             :     }
     624         216 :     while ((samples.size()*2) < value.size()) {
     625         198 :         samples.append(qFromLittleEndian<qint16>(value.mid(samples.size()*2,2)));
     626             :     }
     627          54 :     qCDebug(lc).noquote() << tr("Read %n sample/s from %1-bytes.", nullptr, samples.size()).arg(value.size());
     628           9 :     return samples;
     629           0 : }
     630             : 
     631             : /*!
     632             :  * Implements AbstractPokitServicePrivate::characteristicRead to parse \a value, then emit a
     633             :  * specialised signal, for each supported \a characteristic.
     634             :  */
     635          18 : void DataLoggerServicePrivate::characteristicRead(const QLowEnergyCharacteristic &characteristic,
     636             :                                               const QByteArray &value)
     637             : {
     638          18 :     AbstractPokitServicePrivate::characteristicRead(characteristic, value);
     639             : 
     640          18 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::settings) {
     641           0 :         qCWarning(lc).noquote() << tr("Settings characteristic is write-only, but somehow read")
     642           0 :             << serviceUuid << characteristic.name() << characteristic.uuid();
     643           0 :         return;
     644             :     }
     645             : 
     646             :     Q_Q(DataLoggerService);
     647          18 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::metadata) {
     648           0 :         emit q->metadataRead(parseMetadata(value));
     649           0 :         return;
     650             :     }
     651             : 
     652          18 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::reading) {
     653           0 :         qCWarning(lc).noquote() << tr("Reading characteristic is notify-only")
     654           0 :             << serviceUuid << characteristic.name() << characteristic.uuid();
     655           0 :         return;
     656             :     }
     657             : 
     658          54 :     qCWarning(lc).noquote() << tr("Unknown characteristic read for Data Logger service")
     659          24 :         << serviceUuid << characteristic.name() << characteristic.uuid();
     660             : }
     661             : 
     662             : /*!
     663             :  * Implements AbstractPokitServicePrivate::characteristicWritten to parse \a newValue, then emit a
     664             :  * specialised signal, for each supported \a characteristic.
     665             :  */
     666          18 : void DataLoggerServicePrivate::characteristicWritten(const QLowEnergyCharacteristic &characteristic,
     667             :                                                      const QByteArray &newValue)
     668             : {
     669          18 :     AbstractPokitServicePrivate::characteristicWritten(characteristic, newValue);
     670             : 
     671             :     Q_Q(DataLoggerService);
     672          18 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::settings) {
     673           0 :         emit q->settingsWritten();
     674           0 :         return;
     675             :     }
     676             : 
     677          18 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::metadata) {
     678           0 :         qCWarning(lc).noquote() << tr("Metadata characteristic is read/notify, but somehow written")
     679           0 :             << serviceUuid << characteristic.name() << characteristic.uuid();
     680           0 :         return;
     681             :     }
     682             : 
     683          18 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::reading) {
     684           0 :         qCWarning(lc).noquote() << tr("Reading characteristic is notify-only, but somehow written")
     685           0 :             << serviceUuid << characteristic.name() << characteristic.uuid();
     686           0 :         return;
     687             :     }
     688             : 
     689          54 :     qCWarning(lc).noquote() << tr("Unknown characteristic written for Data Logger service")
     690          24 :         << serviceUuid << characteristic.name() << characteristic.uuid();
     691             : }
     692             : 
     693             : /*!
     694             :  * Implements AbstractPokitServicePrivate::characteristicChanged to parse \a newValue, then emit a
     695             :  * specialised signal, for each supported \a characteristic.
     696             :  */
     697          18 : void DataLoggerServicePrivate::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
     698             :                                                      const QByteArray &newValue)
     699             : {
     700          18 :     AbstractPokitServicePrivate::characteristicChanged(characteristic, newValue);
     701             : 
     702             :     Q_Q(DataLoggerService);
     703          18 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::settings) {
     704           0 :         qCWarning(lc).noquote() << tr("Settings characteristic is write-only, but somehow updated")
     705           0 :             << serviceUuid << characteristic.name() << characteristic.uuid();
     706           0 :         return;
     707             :     }
     708             : 
     709          18 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::metadata) {
     710           0 :         emit q->metadataRead(parseMetadata(newValue));
     711           0 :         return;
     712             :     }
     713             : 
     714          18 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::reading) {
     715           0 :         emit q->samplesRead(parseSamples(newValue));
     716           0 :         return;
     717             :     }
     718             : 
     719          54 :     qCWarning(lc).noquote() << tr("Unknown characteristic notified for Data Logger service")
     720          24 :         << serviceUuid << characteristic.name() << characteristic.uuid();
     721             : }
     722             : 
     723             : /// \endcond

Generated by: LCOV version 1.14