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

Generated by: LCOV version 1.14