LCOV - code coverage report
Current view: top level - src/lib - dsoservice.cpp (source / functions) Hit Total Coverage
Project: Dokit Lines: 173 205 84.4 %
Version: Functions: 41 45 91.1 %

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

Generated by: LCOV version 1.14