LCOV - code coverage report
Current view: top level - src/lib - dataloggerservice.cpp (source / functions) Coverage Total Hit
Project: Dokit Lines: 83.0 % 264 219
Version: Functions: 91.1 % 56 51

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

Generated by: LCOV version 2.2-1