LCOV - code coverage report
Current view: top level - src/lib - dataloggerservice.cpp (source / functions) Hit Total Coverage
Project: Dokit Lines: 123 156 78.8 %
Version: Functions: 27 57 47.4 %

          Line data    Source code
       1             : // SPDX-FileCopyrightText: 2022-2024 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             : #include "pokitproducts_p.h"
      12             : 
      13             : #include <qtpokit/statusservice.h>
      14             : 
      15             : #include <QDataStream>
      16             : #include <QIODevice>
      17             : #include <QLowEnergyController>
      18             : #include <QtEndian>
      19             : 
      20             : /*!
      21             :  * \class DataLoggerService
      22             :  *
      23             :  * The DataLoggerService class accesses the `Data Logger` service of Pokit devices.
      24             :  */
      25             : 
      26             : /// Returns \a mode as a user-friendly string.
      27        3060 : QString DataLoggerService::toString(const Mode &mode)
      28             : {
      29        3060 :     switch (mode) {
      30          20 :     case Mode::Idle:        return tr("Idle");
      31         600 :     case Mode::DcVoltage:   return tr("DC voltage");
      32         600 :     case Mode::AcVoltage:   return tr("AC voltage");
      33         600 :     case Mode::DcCurrent:   return tr("DC current");
      34         600 :     case Mode::AcCurrent:   return tr("AC current");
      35         600 :     case Mode::Temperature: return tr("Temperature");
      36             :     default:                return QString();
      37             :     }
      38             : }
      39             : 
      40             : /// Returns \a range as a user-friendly string, or a null QString if \a mode has no ranges.
      41        2040 : QString DataLoggerService::toString(const PokitProduct product, const quint8 range, const Mode mode)
      42             : {
      43        2040 :     switch (mode) {
      44             :     case Mode::Idle:
      45             :         break;
      46         800 :     case Mode::DcVoltage:
      47             :     case Mode::AcVoltage:
      48         800 :         return VoltageRange::toString(product, range);
      49         800 :     case Mode::DcCurrent:
      50             :     case Mode::AcCurrent:
      51         800 :         return CurrentRange::toString(product, range);
      52             :     case Mode::Temperature:
      53             :         break;
      54             :     }
      55             :     return QString();
      56             : }
      57             : 
      58             : /// Returns \a range as a user-friendly string, or a null QString if \a mode has no ranges.
      59        1920 : QString DataLoggerService::toString(const quint8 range, const Mode mode) const
      60             : {
      61        1920 :     return toString(*pokitProduct(), range, mode);
      62             : }
      63             : 
      64             : /*!
      65             :  *  Returns the maximum value for \a range, or the string "Auto".
      66             :  *
      67             :  *  If \a range is not a known valid enumeration value for \a product's \a mode, then a null QVariant is returned.
      68             :  */
      69         240 : QVariant DataLoggerService::maxValue(const PokitProduct product, const quint8 range, const Mode mode)
      70             : {
      71         240 :     switch (mode) {
      72             :     case Mode::Idle:
      73             :         break;
      74          80 :     case Mode::DcVoltage:
      75             :     case Mode::AcVoltage:
      76          80 :         return VoltageRange::maxValue(product, range);
      77          80 :     case Mode::DcCurrent:
      78             :     case Mode::AcCurrent:
      79          80 :         return CurrentRange::maxValue(product, range);
      80             :     case Mode::Temperature:
      81             :         break;
      82             :     }
      83             :     return QVariant();
      84             : }
      85             : 
      86             : /*!
      87             :  *  Returns the maximum value for \a range, or the string "Auto".
      88             :  *
      89             :  *  If \a range is not a known valid enumeration value for the current \a product's \a mode,
      90             :  *  then a null QVariant is returned.
      91             :  */
      92         120 : QVariant DataLoggerService::maxValue(const quint8 range, const Mode mode) const
      93             : {
      94         120 :     return maxValue(*pokitProduct(), range, mode);
      95             : }
      96             : 
      97             : /*!
      98             :  * \typedef DataLoggerService::Samples
      99             :  *
     100             :  * Raw samples from the `Reading` characteristic. These raw samples are (supposedly) within the
     101             :  * range -2048 to +2047, and need to be multiplied by the Metadata::scale value from the `Metadata`
     102             :  * characteristc to get the true values.
     103             :  *
     104             :  * Also supposedly, there should be no more than 10 samples at a time, according to Pokit's current
     105             :  * API docs. There is not artificial limitation imposed by QtPokit, so devices may begin batching
     106             :  * more samples in future. Specifically, the Pokit Pro seems to send 88 samples (in 176 bytes) at a
     107             :  * time.
     108             :  */
     109             : 
     110             : /*!
     111             :  * Constructs a new Pokit service with \a parent.
     112             :  */
     113        2060 : DataLoggerService::DataLoggerService(QLowEnergyController * const controller, QObject * parent)
     114        2060 :     : AbstractPokitService(new DataLoggerServicePrivate(controller, this), parent)
     115             : {
     116             : 
     117        2060 : }
     118             : 
     119             : /*!
     120             :  * \cond internal
     121             :  * Constructs a new Pokit service with \a parent, and private implementation \a d.
     122             :  */
     123           0 : DataLoggerService::DataLoggerService(
     124           0 :     DataLoggerServicePrivate * const d, QObject * const parent)
     125           0 :     : AbstractPokitService(d, parent)
     126             : {
     127             : 
     128           0 : }
     129             : /// \endcond
     130             : 
     131             : /*!
     132             :  * Destroys this DataLoggerService object.
     133             :  */
     134         540 : DataLoggerService::~DataLoggerService()
     135             : {
     136             : 
     137         540 : }
     138             : 
     139          20 : bool DataLoggerService::readCharacteristics()
     140             : {
     141          20 :     return readMetadataCharacteristic();
     142             : }
     143             : 
     144             : /*!
     145             :  * Reads the `DataLogger` service's `Metadata` characteristic.
     146             :  *
     147             :  * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
     148             :  * underlying controller it not yet connected to the Pokit device, or the device's services have
     149             :  * not yet been discovered).
     150             :  *
     151             :  * Emits metadataRead() if/when the characteristic has been read successfully.
     152             :  */
     153          34 : bool DataLoggerService::readMetadataCharacteristic()
     154             : {
     155             :     Q_D(DataLoggerService);
     156          40 :     return d->readCharacteristic(CharacteristicUuids::metadata);
     157             : }
     158             : 
     159             : /*!
     160             :  * Configures the Pokit device's data logger mode.
     161             :  *
     162             :  * Returns `true` if the write request was successfully queued, `false` otherwise.
     163             :  *
     164             :  * Emits settingsWritten() if/when the \a settings have been writtem successfully.
     165             :  */
     166          80 : bool DataLoggerService::setSettings(const Settings &settings)
     167             : {
     168             :     Q_D(const DataLoggerService);
     169             :     const QLowEnergyCharacteristic characteristic =
     170          80 :         d->getCharacteristic(CharacteristicUuids::settings);
     171          80 :     if (!characteristic.isValid()) {
     172             :         return false;
     173             :     }
     174             : 
     175             :     const bool updateIntervalIs32bit =
     176           0 :         (d->getCharacteristic(CharacteristicUuids::metadata).value().size() >= 23);
     177           0 :     const QByteArray value = DataLoggerServicePrivate::encodeSettings(settings, updateIntervalIs32bit);
     178           0 :     if (value.isNull()) {
     179             :         return false;
     180             :     }
     181             : 
     182           0 :     d->service->writeCharacteristic(characteristic, value);
     183           0 :     return (d->service->error() != QLowEnergyService::ServiceError::CharacteristicWriteError);
     184          80 : }
     185             : 
     186             : /*!
     187             :  * Start the data logger with \a settings.
     188             :  *
     189             :  * This is just a synonym for setSettings() except makes the caller's intention more explicit, and
     190             :  * sanity-checks that the settings's command is DataLoggerService::Command::Start.
     191             :  */
     192          60 : bool DataLoggerService::startLogger(const Settings &settings)
     193             : {
     194             :     Q_D(const DataLoggerService);
     195             :     Q_ASSERT(settings.command == DataLoggerService::Command::Start);
     196          60 :     if (settings.command != DataLoggerService::Command::Start) {
     197          96 :         qCWarning(d->lc).noquote() << tr("Settings command must be 'Start'.");
     198          36 :         return false;
     199             :     }
     200          20 :     return setSettings(settings);
     201             : }
     202             : 
     203             : /*!
     204             :  * Stop the data logger.
     205             :  *
     206             :  * This is just a convenience function equivalent to calling setSettings() with the command set to
     207             :  * DataLoggerService::Command::Stop.
     208             :  */
     209          20 : bool DataLoggerService::stopLogger()
     210             : {
     211             :     // Note, only the Settings::command member need be set, since the others are all ignored by the
     212             :     // Pokit device when the command is Stop. However, we still explicitly initialise all other
     213             :     // members just to ensure we're never exposing uninitialised RAM to an external device.
     214          20 :     return setSettings({ DataLoggerService::Command::Stop,  0, DataLoggerService::Mode::Idle, 0, 0, 0 });
     215             : }
     216             : 
     217             : /*!
     218             :  * Start the data logger.
     219             :  *
     220             :  * This is just a convenience function equivalent to calling setSettings() with the command set to
     221             :  * DataLoggerService::Command::Refresh.
     222             :  *
     223             :  * Once the Pokit device has processed this request succesffully, the device will begin notifying
     224             :  * the `Metadata` and `Reading` characteristic, resulting in emits of metadataRead and samplesRead
     225             :  * respectively.
     226             :  */
     227          20 : bool DataLoggerService::fetchSamples()
     228             : {
     229             :     // Note, only the Settings::command member need be set, since the others are all ignored by the
     230             :     // Pokit device when the command is Refresh. However, we still explicitly initialise all other
     231             :     // members just to ensure we're never exposing uninitialised RAM to an external device.
     232          20 :     return setSettings({ DataLoggerService::Command::Refresh, 0, DataLoggerService::Mode::Idle, 0, 0, 0 });
     233             : }
     234             : 
     235             : /*!
     236             :  * Returns the most recent value of the `DataLogger` service's `Metadata` characteristic.
     237             :  *
     238             :  * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
     239             :  * currently available (ie the serviceDetailsDiscovered signal has not been emitted yet), then the
     240             :  * returned DataLoggerService::Metadata::scale member will be a quiet NaN, which can be checked like:
     241             :  *
     242             :  * ```
     243             :  * const DataLoggerService::Metadata metadata = multimeterService->metadata();
     244             :  * if (qIsNaN(metadata.scale)) {
     245             :  *     // Handle failure.
     246             :  * }
     247             :  * ```
     248             :  */
     249          20 : DataLoggerService::Metadata DataLoggerService::metadata() const
     250             : {
     251             :     Q_D(const DataLoggerService);
     252             :     const QLowEnergyCharacteristic characteristic =
     253          20 :         d->getCharacteristic(CharacteristicUuids::metadata);
     254          20 :     return (characteristic.isValid()) ? DataLoggerServicePrivate::parseMetadata(characteristic.value())
     255          40 :         : Metadata{ LoggerStatus::Error, std::numeric_limits<float>::quiet_NaN(), Mode::Idle, 0, 0, 0, 0 };
     256          20 : }
     257             : 
     258             : /*!
     259             :  * Enables client-side notifications of Data Logger metadata changes.
     260             :  *
     261             :  * This is an alternative to manually requesting individual reads via readMetadataCharacteristic().
     262             :  *
     263             :  * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
     264             :  *
     265             :  * Successfully read values (if any) will be emitted via the metadataRead() signal.
     266             :  */
     267          20 : bool DataLoggerService::enableMetadataNotifications()
     268             : {
     269             :     Q_D(DataLoggerService);
     270          20 :     return d->enableCharacteristicNotificatons(CharacteristicUuids::metadata);
     271             : }
     272             : 
     273             : /*!
     274             :  * Disables client-side notifications of Data Logger metadata changes.
     275             :  *
     276             :  * Instantaneous reads can still be fetched by readMetadataCharacteristic().
     277             :  *
     278             :  * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
     279             :  */
     280          20 : bool DataLoggerService::disableMetadataNotifications()
     281             : {
     282             :     Q_D(DataLoggerService);
     283          20 :     return d->disableCharacteristicNotificatons(CharacteristicUuids::metadata);
     284             : }
     285             : 
     286             : /*!
     287             :  * Enables client-side notifications of Data Logger readings.
     288             :  *
     289             :  * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
     290             :  *
     291             :  * Successfully read samples (if any) will be emitted via the samplesRead() signal.
     292             :  */
     293          20 : bool DataLoggerService::enableReadingNotifications()
     294             : {
     295             :     Q_D(DataLoggerService);
     296          20 :     return d->enableCharacteristicNotificatons(CharacteristicUuids::reading);
     297             : }
     298             : 
     299             : /*!
     300             :  * Disables client-side notifications of Data Logger readings.
     301             :  *
     302             :  * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
     303             :  */
     304          20 : bool DataLoggerService::disableReadingNotifications()
     305             : {
     306             :     Q_D(DataLoggerService);
     307          20 :     return d->disableCharacteristicNotificatons(CharacteristicUuids::reading);
     308             : }
     309             : 
     310             : /*!
     311             :  * \fn DataLoggerService::settingsWritten
     312             :  *
     313             :  * This signal is emitted when the `Settings` characteristic has been written successfully.
     314             :  *
     315             :  * \see setSettings
     316             :  */
     317             : 
     318             : /*!
     319             :  * \fn DataLoggerService::metadataRead
     320             :  *
     321             :  * This signal is emitted when the `Metadata` characteristic has been read successfully.
     322             :  *
     323             :  * \see readMetadataCharacteristic
     324             :  */
     325             : 
     326             : /*!
     327             :  * \fn DataLoggerService::samplesRead
     328             :  *
     329             :  * This signal is emitted when the `Reading` characteristic has been notified.
     330             :  *
     331             :  * \see beginSampling
     332             :  * \see stopSampling
     333             :  */
     334             : 
     335             : 
     336             : /*!
     337             :  * \cond internal
     338             :  * \class DataLoggerServicePrivate
     339             :  *
     340             :  * The DataLoggerServicePrivate class provides private implementation for DataLoggerService.
     341             :  */
     342             : 
     343             : /*!
     344             :  * \internal
     345             :  * Constructs a new DataLoggerServicePrivate object with public implementation \a q.
     346             :  */
     347        1442 : DataLoggerServicePrivate::DataLoggerServicePrivate(
     348        2060 :     QLowEnergyController * controller, DataLoggerService * const q)
     349        2060 :     : AbstractPokitServicePrivate(DataLoggerService::serviceUuid, controller, q)
     350             : {
     351             : 
     352        1442 : }
     353             : 
     354             : /*!
     355             :  * Returns \a settings in the format Pokit devices expect. If \a updateIntervalIs32bit is \c true
     356             :  * then the `Update Interval` field will be encoded in 32-bit instead of 16.
     357             :  */
     358         140 : QByteArray DataLoggerServicePrivate::encodeSettings(const DataLoggerService::Settings &settings,
     359             :                                                     const bool updateIntervalIs32bit)
     360             : {
     361             :     static_assert(sizeof(settings.command)        == 1, "Expected to be 1 byte.");
     362             :     static_assert(sizeof(settings.arguments)      == 2, "Expected to be 2 bytes.");
     363             :     static_assert(sizeof(settings.mode)           == 1, "Expected to be 1 byte.");
     364             :     static_assert(sizeof(settings.range)          == 1, "Expected to be 1 byte.");
     365             :     static_assert(sizeof(settings.updateInterval) == 4, "Expected to be 4 bytes.");
     366             :     static_assert(sizeof(settings.timestamp)      == 4, "Expected to be 4 bytes.");
     367             : 
     368          42 :     QByteArray value;
     369         140 :     QDataStream stream(&value, QIODevice::WriteOnly);
     370         140 :     stream.setByteOrder(QDataStream::LittleEndian);
     371         140 :     stream.setFloatingPointPrecision(QDataStream::SinglePrecision); // 32-bit floats, not 64-bit.
     372         147 :     stream << (quint8)settings.command << settings.arguments << (quint8)settings.mode << settings.range;
     373             : 
     374             :     /*!
     375             :      * \pokitApi For Pokit Meter, `updateInterval` is `uint16` seconds (as per the Pokit API 1.00),
     376             :      * however for Pokit Pro it's `uint32` milliseconds, even though that's not officially
     377             :      * documented anywhere.
     378             :      */
     379             : 
     380         140 :     if (!updateIntervalIs32bit) {
     381          40 :         stream << (quint16)((settings.updateInterval+500)/1000) << settings.timestamp;
     382             :         Q_ASSERT(value.size() == 11); // According to Pokit API 1.00.
     383             :     } else {
     384         100 :         stream << settings.updateInterval << settings.timestamp;
     385             :         Q_ASSERT(value.size() == 13); // According to testing / experimentation.
     386             :     }
     387         140 :     return value;
     388         140 : }
     389             : 
     390             : /*!
     391             :  * Parses the `Metadata` \a value into a DataLoggerService::Metatdata struct.
     392             :  */
     393         100 : DataLoggerService::Metadata DataLoggerServicePrivate::parseMetadata(const QByteArray &value)
     394             : {
     395         100 :     DataLoggerService::Metadata metadata{
     396             :         DataLoggerService::LoggerStatus::Error, std::numeric_limits<float>::quiet_NaN(),
     397             :         DataLoggerService::Mode::Idle, 0, 0, 0, 0
     398             :     };
     399             : 
     400             :     // Pokit Meter: 15 bytes, Pokit Pro: 23 bytes.
     401         130 :     if (!checkSize(QLatin1String("Metadata"), value, 15, 23)) {
     402             :         return metadata;
     403             :     }
     404             : 
     405          66 :     qCDebug(lc) << value.mid(7,12).toHex(',');
     406          60 :     metadata.status = static_cast<DataLoggerService::LoggerStatus>(value.at(0));
     407          78 :     metadata.scale  = qFromLittleEndian<float>(value.mid(1,4).constData());
     408          60 :     metadata.mode   = static_cast<DataLoggerService::Mode>(value.at(5));
     409          60 :     metadata.range  = static_cast<quint8>(value.at(6));
     410             : 
     411             :     /*!
     412             :      * \pokitApi For Pokit Meter, `updateInterval` is `uint16` (as per the Pokit API 1.00), however
     413             :      * for Pokit Pro it's `uint32`, even though that's not officially documented anywhere.
     414             :      * Also note, the doc claims 'microseconds' (ie 10^-6), but clearly the value is 'milliseconds'
     415             :      * (ie 10^-3) for Pokit Pro, and whole seconds for Pokit Meter.
     416             :      */
     417             : 
     418          60 :     if (value.size() == 15) {
     419          26 :         metadata.updateInterval  = qFromLittleEndian<quint16>(value.mid(7,2).constData())*1000;
     420          26 :         metadata.numberOfSamples = qFromLittleEndian<quint16>(value.mid(9,2).constData());
     421          26 :         metadata.timestamp       = qFromLittleEndian<quint32>(value.mid(11,4).constData());
     422          40 :     } else if (value.size() == 23) {
     423          26 :         metadata.updateInterval  = qFromLittleEndian<quint32>(value.mid(7,4).constData());
     424          26 :         metadata.numberOfSamples = qFromLittleEndian<quint16>(value.mid(11,2).constData());
     425          26 :         metadata.timestamp       = qFromLittleEndian<quint32>(value.mid(19,4).constData());
     426             :     } else {
     427          42 :         qCWarning(lc).noquote() << tr("Cannot decode metadata of %n byte/s: %1", nullptr, value.size())
     428          26 :             .arg(toHexString(value));
     429             :     }
     430             :     return metadata;
     431             : }
     432             : 
     433             : /*!
     434             :  * Parses the `Reading` \a value into a DataLoggerService::Samples vector.
     435             :  */
     436          80 : DataLoggerService::Samples DataLoggerServicePrivate::parseSamples(const QByteArray &value)
     437             : {
     438          24 :     DataLoggerService::Samples samples;
     439          80 :     if ((value.size()%2) != 0) {
     440          48 :         qCWarning(lc).noquote() << tr("Samples value has odd size %1 (should be even): %2")
     441          32 :             .arg(value.size()).arg(toHexString(value));
     442           2 :         return samples;
     443             :     }
     444         240 :     while ((samples.size()*2) < value.size()) {
     445         234 :         samples.append(qFromLittleEndian<qint16>(value.mid(samples.size()*2,2).constData()));
     446             :     }
     447          66 :     qCDebug(lc).noquote() << tr("Read %n sample/s from %1-bytes.", nullptr, samples.size()).arg(value.size());
     448           6 :     return samples;
     449           0 : }
     450             : 
     451             : /*!
     452             :  * Implements AbstractPokitServicePrivate::characteristicRead to parse \a value, then emit a
     453             :  * specialised signal, for each supported \a characteristic.
     454             :  */
     455          20 : void DataLoggerServicePrivate::characteristicRead(const QLowEnergyCharacteristic &characteristic,
     456             :                                               const QByteArray &value)
     457             : {
     458          20 :     AbstractPokitServicePrivate::characteristicRead(characteristic, value);
     459             : 
     460          20 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::settings) {
     461           0 :         qCWarning(lc).noquote() << tr("Settings characteristic is write-only, but somehow read")
     462           0 :             << serviceUuid << characteristic.name() << characteristic.uuid();
     463           0 :         return;
     464             :     }
     465             : 
     466             :     Q_Q(DataLoggerService);
     467          20 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::metadata) {
     468           0 :         Q_EMIT q->metadataRead(parseMetadata(value));
     469           0 :         return;
     470             :     }
     471             : 
     472          20 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::reading) {
     473           0 :         qCWarning(lc).noquote() << tr("Reading characteristic is notify-only")
     474           0 :             << serviceUuid << characteristic.name() << characteristic.uuid();
     475           0 :         return;
     476             :     }
     477             : 
     478          62 :     qCWarning(lc).noquote() << tr("Unknown characteristic read for Data Logger service")
     479          26 :         << serviceUuid << characteristic.name() << characteristic.uuid();
     480             : }
     481             : 
     482             : /*!
     483             :  * Implements AbstractPokitServicePrivate::characteristicWritten to parse \a newValue, then emit a
     484             :  * specialised signal, for each supported \a characteristic.
     485             :  */
     486          20 : void DataLoggerServicePrivate::characteristicWritten(const QLowEnergyCharacteristic &characteristic,
     487             :                                                      const QByteArray &newValue)
     488             : {
     489          20 :     AbstractPokitServicePrivate::characteristicWritten(characteristic, newValue);
     490             : 
     491             :     Q_Q(DataLoggerService);
     492          20 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::settings) {
     493           0 :         Q_EMIT q->settingsWritten();
     494           0 :         return;
     495             :     }
     496             : 
     497          20 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::metadata) {
     498           0 :         qCWarning(lc).noquote() << tr("Metadata characteristic is read/notify, but somehow written")
     499           0 :             << serviceUuid << characteristic.name() << characteristic.uuid();
     500           0 :         return;
     501             :     }
     502             : 
     503          20 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::reading) {
     504           0 :         qCWarning(lc).noquote() << tr("Reading characteristic is notify-only, but somehow written")
     505           0 :             << serviceUuid << characteristic.name() << characteristic.uuid();
     506           0 :         return;
     507             :     }
     508             : 
     509          62 :     qCWarning(lc).noquote() << tr("Unknown characteristic written for Data Logger service")
     510          26 :         << serviceUuid << characteristic.name() << characteristic.uuid();
     511             : }
     512             : 
     513             : /*!
     514             :  * Implements AbstractPokitServicePrivate::characteristicChanged to parse \a newValue, then emit a
     515             :  * specialised signal, for each supported \a characteristic.
     516             :  */
     517          20 : void DataLoggerServicePrivate::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
     518             :                                                      const QByteArray &newValue)
     519             : {
     520          20 :     AbstractPokitServicePrivate::characteristicChanged(characteristic, newValue);
     521             : 
     522             :     Q_Q(DataLoggerService);
     523          20 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::settings) {
     524           0 :         qCWarning(lc).noquote() << tr("Settings characteristic is write-only, but somehow updated")
     525           0 :             << serviceUuid << characteristic.name() << characteristic.uuid();
     526           0 :         return;
     527             :     }
     528             : 
     529          20 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::metadata) {
     530           0 :         Q_EMIT q->metadataRead(parseMetadata(newValue));
     531           0 :         return;
     532             :     }
     533             : 
     534          20 :     if (characteristic.uuid() == DataLoggerService::CharacteristicUuids::reading) {
     535           0 :         Q_EMIT q->samplesRead(parseSamples(newValue));
     536           0 :         return;
     537             :     }
     538             : 
     539          62 :     qCWarning(lc).noquote() << tr("Unknown characteristic notified for Data Logger service")
     540          26 :         << serviceUuid << characteristic.name() << characteristic.uuid();
     541             : }
     542             : 
     543             : /// \endcond

Generated by: LCOV version 1.14