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