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