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