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