Dokit
Internal development documentation
Loading...
Searching...
No Matches
statusservice.cpp
Go to the documentation of this file.
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 StatusService and StatusServicePrivate classes.
7 */
8
10#include "statusservice_p.h"
11
12#include <QtEndian>
13
14/*!
15 * \class StatusService
16 *
17 * The StatusService class accesses the `Pokit Status` service of Pokit devices.
18 */
19
20/*!
21 * \cond internal
22 * \struct StatusService::ServiceUuids
23 * \pokitApi Pokit API 1.00 (and 0.02) states the Status Service UUID as
24 * `57d3a771-267c-4394-8872-78223e92aec4` which is correct for the Pokit Meter, but Pokit Pro uses
25 * `57d3a771-267c-4394-8872-78223e92aec5` instead, that is the last digit is a `5` not `4`.
26 * \endcond
27 */
28
29/*!
30 * Returns a string version of the \a status enum label.
31 */
33{
34 switch (status) {
35 case DeviceStatus::Idle: return QLatin1String("Idle");
36 case DeviceStatus::MultimeterDcVoltage: return QLatin1String("MultimeterDcVoltage");
37 case DeviceStatus::MultimeterAcVoltage: return QLatin1String("MultimeterAcVoltage");
38 case DeviceStatus::MultimeterDcCurrent: return QLatin1String("MultimeterDcCurrent");
39 case DeviceStatus::MultimeterAcCurrent: return QLatin1String("MultimeterAcCurrent");
40 case DeviceStatus::MultimeterResistance: return QLatin1String("MultimeterResistance");
41 case DeviceStatus::MultimeterDiode: return QLatin1String("MultimeterDiode");
42 case DeviceStatus::MultimeterContinuity: return QLatin1String("MultimeterContinuity");
43 case DeviceStatus::MultimeterTemperature:return QLatin1String("MultimeterTemperature");
44 case DeviceStatus::DsoModeSampling: return QLatin1String("DsoModeSampling");
45 case DeviceStatus::LoggerModeSampling: return QLatin1String("LoggerModeSampling");
46 }
47 return QString();
48}
49
50/*!
51 * Returns a string version of the \a status enum label.
52 */
54{
55 switch (status) {
56 case BatteryStatus::Low: return QLatin1String("Low");
57 case BatteryStatus::Good: return QLatin1String("Good");
58 }
59 return QString();
60}
61
62/*!
63 * \cond internal
64 * \enum StatusService::SwitchPosition
65 * \pokitApi These enum values are undocumented, but easily testable with a physical Pokit Pro device.
66 * Internally, Pokit's Android app calls these: `SWITCH_MODE_VOLTAGE`, `SWITCH_MODE_ALL` and `SWITCH_MODE_CURRENT`.
67 * \endcond
68 */
69
70/*!
71 * Returns a string version of the \a position enum label.
72 */
74{
75 switch (position) {
76 case SwitchPosition::Voltage: return QLatin1String("Voltage");
77 case SwitchPosition::MultiMode: return QLatin1String("MultiMode");
78 case SwitchPosition::HighCurrent: return QLatin1String("HighCurrent");
79 }
80 return QString();
81}
82
83/*!
84 * Returns a string version of the \a status enum label.
85 */
87{
88 switch (status) {
89 case ChargingStatus::Discharging: return QLatin1String("Discharging");
90 case ChargingStatus::Charging: return QLatin1String("Charging");
91 case ChargingStatus::Charged: return QLatin1String("Charged");
92 }
93 return QString();
94}
95
96/*!
97 * Returns a string version of the \a status enum label.
98 */
100{
101 switch (status) {
102 case TorchStatus::Off: return QLatin1String("Off");
103 case TorchStatus::On: return QLatin1String("On");
104 }
105 return QString();
106}
107
108/*!
109 * Returns a string version of the \a status enum label.
110 */
112{
113 switch (status) {
114 case ButtonStatus::Released: return QLatin1String("Released");
115 case ButtonStatus::Pressed: return QLatin1String("Pressed");
116 case ButtonStatus::Held: return QLatin1String("Held");
117 }
118 return QString();
119}
120
121/*!
122 * Constructs a new Pokit service with \a parent.
123 */
125 : AbstractPokitService(new StatusServicePrivate(controller, this), parent)
126{
127
128}
129
130/*!
131 * \cond internal
132 * Constructs a new Pokit service with \a parent, and private implementation \a d.
133 */
135 StatusServicePrivate * const d, QObject * const parent)
136 : AbstractPokitService(d, parent)
137{
138
139}
140/// \endcond
141
142/*!
143 * Destroys this StatusService object.
144 */
149
151{
152 const bool r1 = readDeviceCharacteristics();
153 const bool r2 = readStatusCharacteristic();
154 const bool r3 = readNameCharacteristic();
155 const bool r4 = ((service() != nullptr) && (service()->characteristic(CharacteristicUuids::torch).isValid()))
156 ? readTorchCharacteristic() : true;
157 const bool r5 = ((service() != nullptr) && (service()->characteristic(CharacteristicUuids::buttonPress).isValid()))
159 return (r1 && r2 && r3 && r4 && r5);
160}
161
162/*!
163 * Read the `Status` service's `Device Characteristics` characteristic.
164 *
165 * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
166 * underlying controller it not yet connected to the Pokit device, or the device's services have
167 * not yet been discovered).
168 *
169 * Emits deviceCharacteristicsRead() if/when the characteristic has been read successfully.
170 */
176
177/*!
178 * Read the `Status` service's `Status` characteristic.
179 *
180 * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
181 * underlying controller it not yet connected to the Pokit device, or the device's services have
182 * not yet been discovered).
183 *
184 * Emits deviceStatusRead() if/when the characteristic has been read successfully.
185 */
187{
188 Q_D(StatusService);
189 return d->readCharacteristic(CharacteristicUuids::status);
190}
191
192/*!
193 * Read the `Status` service's `Name` characteristic.
194 *
195 * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
196 * underlying controller it not yet connected to the Pokit device, or the device's services have
197 * not yet been discovered).
198 *
199 * Emits deviceNameRead() if/when the characteristic has been read successfully.
200 */
202{
203 Q_D(StatusService);
204 return d->readCharacteristic(CharacteristicUuids::name);
205}
206
207/*!
208 * Read the `Status` service's (undocumented) `Torch` characteristic.
209 *
210 * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
211 * underlying controller it not yet connected to the Pokit device, or the device's services have
212 * not yet been discovered).
213 *
214 * Emits torchStatusRead() if/when the characteristic has been read successfully.
215 */
217{
218 Q_D(StatusService);
219 return d->readCharacteristic(CharacteristicUuids::torch);
220}
221
222/*!
223 * Read the `Status` service's (undocumented) `Button Press` characteristic.
224 *
225 * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
226 * underlying controller it not yet connected to the Pokit device, or the device's services have
227 * not yet been discovered).
228 *
229 * Emits buttonPressRead() if/when the characteristic has been read successfully.
230 */
232{
233 Q_D(StatusService);
234 return d->readCharacteristic(CharacteristicUuids::buttonPress);
235}
236
237/*!
238 * Returns the most recent value of the `Status` service's `Device Characteristics` characteristic.
239 *
240 * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
241 * currently available (ie the serviceDetailsDiscovered signal has not been emitted yet), then a
242 * null result is returned, which can be checked via the returned
243 * DeviceCharacteristics::firmwareVersion, like:
244 *
245 * ```
246 * const DeviceCharacteristics characteristics = service->deviceCharacteristics();
247 * if (!characteristics.firmwareVersion.isNull()) {
248 * ...
249 * }
250 * ```
251 */
261
262/*!
263 * Returns the most recent value of the `Status` service's `Status` characteristic.
264 *
265 * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
266 * currently available (ie the serviceDetailsDiscovered signal has not been emitted yet), then the
267 * returned StatusService::Status::batteryLevel member will be a quiet NaN, which can be checked
268 * like:
269 *
270 * ```
271 * const StatusService::Status status = statusService->status();
272 * if (qIsNaN(status.batteryVoltage)) {
273 * // Handle failure.
274 * }
275 * ```
276 *
277 * Not all Pokit devices support the Status::batteryStatus member, in which case the member will be
278 * initilialised to the maximum value supported by the underlying type (ie `255`) to indicate "not set"
279 */
281{
282 Q_D(const StatusService);
283 const QLowEnergyCharacteristic characteristic =
284 d->getCharacteristic(CharacteristicUuids::status);
285 return (characteristic.isValid()) ? StatusServicePrivate::parseStatus(characteristic.value())
286 : StatusService::Status{ DeviceStatus::Idle, std::numeric_limits<float>::quiet_NaN(),
287 BatteryStatus::Low, std::nullopt, std::nullopt };
288}
289
290/*!
291 * Enables client-side notifications of device status changes.
292 *
293 * This is an alternative to manually requesting individual reads via readStatusCharacteristic().
294 *
295 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
296 *
297 * Successfully read values (if any) will be emitted via the deviceStatusRead() signal.
298 */
300{
301 Q_D(StatusService);
302 return d->enableCharacteristicNotificatons(CharacteristicUuids::status);
303}
304
305/*!
306 * Disables client-side notifications of device status changes.
307 *
308 * Instantaneous status can still be fetched by readStatusCharacteristic().
309 *
310 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
311 */
313{
314 Q_D(StatusService);
315 return d->disableCharacteristicNotificatons(CharacteristicUuids::status);
316}
317
318/*!
319 * Returns the most recent value of the `Status` services's `Device Name` characteristic.
320 *
321 * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
322 * currently available (ie the serviceDetailsDiscovered signal has not been emitted yet), then a
323 * null QString is returned.
324 */
326{
327 Q_D(const StatusService);
328 const QLowEnergyCharacteristic characteristic =
329 d->getCharacteristic(CharacteristicUuids::name);
330 return (characteristic.isValid()) ? QString::fromUtf8(characteristic.value()) : QString();
331}
332
333/*!
334 * Set's the Pokit device's name to \a name.
335 *
336 * Returns `true` if the write request was successfully queued, `false` otherwise.
337 *
338 * Emits deviceNameWritten() if/when the \a name has been set.
339 */
341{
342 Q_D(const StatusService);
343 const QLowEnergyCharacteristic characteristic =
344 d->getCharacteristic(CharacteristicUuids::name);
345 if (!characteristic.isValid()) {
346 return false;
347 }
348
349 const QByteArray value = name.toUtf8();
350 if (value.length() > 11) {
351 qCWarning(d->lc).noquote() << tr(R"(Device name "%1" is too long (%2 > 11 bytes): 0x3)")
352 .arg(name).arg(value.length()).arg(QLatin1String(value.toHex()));
353 return false;
354 }
355
356 d->service->writeCharacteristic(characteristic, value);
357 return (d->service->error() != QLowEnergyService::ServiceError::CharacteristicWriteError);
358}
359
360/*!
361 * Flash the Pokit device's LED.
362 *
363 * Returns `true` if the flash request was successfully queued, `false` otherwise.
364 *
365 * Emits deviceLedFlashed() if/when the LED has flashed successfully.
366 *
367 * \note This operation is only supported by Pokit Meter devices. Pokit Pro devices will report an
368 * Bluetooth ATT error `0x80`.
369 *
370 * \cond internal
371 * \pokitApi The Android app can turn Pokit Pro LEDs on/off. Perhaps that is handled by an
372 * undocumented use of this characteristic. Or perhaps its via some other service.
373 * \endcond
374 */
376{
377 Q_D(const StatusService);
378 const QLowEnergyCharacteristic characteristic =
379 d->getCharacteristic(CharacteristicUuids::flashLed);
380 if (!characteristic.isValid()) {
381 return false;
382 }
383
384 // The Flash LED characeristic is write-only, and takes a single uint8 "LED" parameter, which
385 // must always be 1. Presumably this is an index for which LED to flash, but the Pokit API docs
386 // say that "any value other than 1 will be ignored", which makes sense given that all current
387 // Pokit devices have only one LED.
388 const QByteArray value(1, '\x01');
389 d->service->writeCharacteristic(characteristic, value);
390 return (d->service->error() != QLowEnergyService::ServiceError::CharacteristicWriteError);
391}
392
393/*!
394 * Returns the most recent value of the `Status` services's `Torch` characteristic.
395 *
396 * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
397 * currently available (eg if the device does not support the Torch characteristic), then `nullopt`
398 * is returned.
399 */
400std::optional<StatusService::TorchStatus> StatusService::torchStatus() const
401{
402 Q_D(const StatusService);
403 const QLowEnergyCharacteristic characteristic = d->getCharacteristic(CharacteristicUuids::torch);
404 return (characteristic.isValid()) ? StatusServicePrivate::parseTorchStatus(characteristic.value()) : std::nullopt;
405}
406
407/*!
408 * Set the Pokit device's torch to \a status.
409 *
410 * Returns `true` if the request was successfully queued, `false` otherwise.
411 *
412 * Emits torchStatusWritten() if/when the LED has flashed successfully.
413 *
414 * \note This operation is only supported by Pokit Pro devices, and not Pokit Meter devices.
415 */
417{
418 Q_D(const StatusService);
419 const QLowEnergyCharacteristic characteristic = d->getCharacteristic(CharacteristicUuids::torch);
420 if (!characteristic.isValid()) {
421 return false;
422 }
423
424 const QByteArray value(1, static_cast<char>(status));
425 d->service->writeCharacteristic(characteristic, value);
426 return (d->service->error() != QLowEnergyService::ServiceError::CharacteristicWriteError);
427}
428
429/*!
430 * Enables client-side notifications of torch status changes.
431 *
432 * This is an alternative to manually requesting individual reads via readTorchCharacteristic().
433 *
434 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
435 *
436 * Successfully read values (if any) will be emitted via the torchStatusRead() signal.
437 */
439{
440 Q_D(StatusService);
441 return d->enableCharacteristicNotificatons(CharacteristicUuids::torch);
442}
443
444/*!
445 * Disables client-side notifications of torch status changes.
446 *
447 * Instantaneous torch status can still be fetched by readTorchCharacteristic().
448 *
449 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
450 */
452{
453 Q_D(StatusService);
454 return d->disableCharacteristicNotificatons(CharacteristicUuids::torch);
455}
456
457/*!
458 * Enables client-side notifications of button presses.
459 *
460 * This is an alternative to manually requesting individual reads via readButtonPressCharacteristic().
461 *
462 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
463 *
464 * Successfully read values (if any) will be emitted via the torchStatusRead() signal.
465 */
467{
468 Q_D(StatusService);
469 return d->enableCharacteristicNotificatons(CharacteristicUuids::buttonPress);
470}
471
472/*!
473 * Disables client-side notifications of button presses.
474 *
475 * Instantaneous button press statussed can still be fetched by readButtonPressCharacteristic().
476 *
477 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
478 */
480{
481 Q_D(StatusService);
482 return d->disableCharacteristicNotificatons(CharacteristicUuids::buttonPress);
483}
484
485/*!
486 * Returns the most recent value of the `Status` services's `Button Press` characteristic.
487 *
488 * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
489 * currently available (eg if the device does not support the Torch characteristic), then `nullopt`
490 * is returned.
491 */
492std::optional<StatusService::ButtonStatus> StatusService::buttonPress() const
493{
494 Q_D(const StatusService);
495 const QLowEnergyCharacteristic characteristic = d->getCharacteristic(CharacteristicUuids::buttonPress);
496 return (characteristic.isValid()) ? StatusServicePrivate::parseButtonPress(characteristic.value()) : std::nullopt;
497}
498
499/*!
500 * \fn StatusService::deviceCharacteristicsRead
501 *
502 * This signal is emitted when the `Device Characteristics` characteristic has been read
503 * successfully.
504 *
505 * \see readDeviceCharacteristics
506 */
507
508/*!
509 * \fn StatusService::deviceNameRead
510 *
511 * This signal is emitted when the `Device Name` characteristic has been read successfully.
512 *
513 * \see readDeviceName
514 */
515
516/*!
517 * \fn StatusService::deviceNameWritten
518 *
519 * This signal is emitted when the `Device Name` characteristic has been written successfully.
520 *
521 * \see setDeviceName
522 */
523
524/*!
525 * \fn StatusService::deviceStatusRead
526 *
527 * This signal is emitted when the `Status` characteristic has been read successfully.
528 *
529 * \see readDeviceStatus
530 */
531
532/*!
533 * \fn StatusService::deviceLedFlashed
534 *
535 * This signal is emitted when device's LED has flashed in response to a write of the `Flash LED`
536 * characteristic.
537 */
538
539/*!
540 * \fn StatusService::torchStatusRead
541 *
542 * This signal is emitted when the `Torch` characteristic has been read successfully.
543 *
544 * \see setTorchStatus
545 */
546
547/*!
548 * \fn StatusService::torchStatusWritten
549 *
550 * This signal is emitted when the `Torch` characteristic has been written successfully.
551 *
552 * \see readTorchCharacteristic
553 */
554
555/*!
556 * \fn StatusService::buttonPressRead
557 *
558 * This signal is emitted when the `Button Press` characteristic has been read successfully.
559 *
560 * \see readButtonPressCharacteristic
561 */
562
563/*!
564 * \cond internal
565 * \class StatusServicePrivate
566 *
567 * The StatusServicePrivate class provides private implementation for StatusService.
568 */
569
570/*!
571 * \internal
572 * Constructs a new StatusServicePrivate object with public implementation \a q.
573 */
580
581/*!
582 * Parses the `Device Characteristics` \a value into a DeviceCharacteristics struct.
583 */
585 const QByteArray &value)
586{
588 QVersionNumber(), 0, 0, 0, 0, 0, 0, QBluetoothAddress()
589 };
590 Q_ASSERT(characteristics.firmwareVersion.isNull()); // How we indicate failure.
591
592 if (!checkSize(QLatin1String("Device Characterisitcs"), value, 20, 20)) {
593 return characteristics;
594 }
595
596 characteristics.firmwareVersion = QVersionNumber(
597 qFromLittleEndian<quint8 >(value.mid(0,1).constData()),
598 qFromLittleEndian<quint8 >(value.mid(1,1).constData()));
599 characteristics.maximumVoltage = qFromLittleEndian<quint16>(value.mid(2,2).constData());
600 characteristics.maximumCurrent = qFromLittleEndian<quint16>(value.mid(4,2).constData());
601 characteristics.maximumResistance = qFromLittleEndian<quint16>(value.mid(6,2).constData());
602 characteristics.maximumSamplingRate = qFromLittleEndian<quint16>(value.mid(8,2).constData());
603 characteristics.samplingBufferSize = qFromLittleEndian<quint16>(value.mid(10,2).constData());
604 characteristics.capabilityMask = qFromLittleEndian<quint16>(value.mid(12,2).constData());
605 characteristics.macAddress = QBluetoothAddress(qFromBigEndian<quint64>
606 ((QByteArray(2, '\0') + value.mid(14,6)).constData()));
607
608 qCDebug(lc).noquote() << tr("Firmware version: ") << characteristics.firmwareVersion;
609 qCDebug(lc).noquote() << tr("Maximum voltage: ") << characteristics.maximumVoltage;
610 qCDebug(lc).noquote() << tr("Maximum current: ") << characteristics.maximumCurrent;
611 qCDebug(lc).noquote() << tr("Maximum resistance: ") << characteristics.maximumResistance;
612 qCDebug(lc).noquote() << tr("Maximum sampling rate:") << characteristics.maximumSamplingRate;
613 qCDebug(lc).noquote() << tr("Sampling buffer size: ") << characteristics.samplingBufferSize;
614 qCDebug(lc).noquote() << tr("Capability mask: ") << characteristics.capabilityMask;
615 qCDebug(lc).noquote() << tr("MAC address: ") << characteristics.macAddress;
616
617 Q_ASSERT(!characteristics.firmwareVersion.isNull()); // How we indicate success.
618 return characteristics;
619}
620
621/*!
622 * Parses the `Status` \a value into a Status struct. Note, not all Pokit devices support all members
623 * in Status. Specifically, the batteryStatus member is not usually set by Pokit Meter devices, so
624 * will be an invlalid BatteryStatus enum value (`255`) in that case.
625 */
627{
629 static_cast<StatusService::DeviceStatus>
630 (std::numeric_limits<std::underlying_type_t<StatusService::DeviceStatus>>::max()),
631 std::numeric_limits<float>::quiet_NaN(),
633 (std::numeric_limits<std::underlying_type_t<StatusService::BatteryStatus>>::max()),
634 std::nullopt, std::nullopt,
635 };
636
637 /*!
638 * \pokitApi Pokit API 0.02 says the `Status` characteristic is 5 bytes. API 1.00 then added an
639 * additional byte for `Battery Status`, for 6 bytes in total. However, Pokit Pro devices return
640 * 8 bytes here. It appears that the first of those 2 extra bytes is used to indicate the physical
641 * switch position, while the other extra byte indicates the device's current charging status.
642 */
643
644 if (!checkSize(QLatin1String("Status"), value, 5, 8)) {
645 return status;
646 }
647
648 status.deviceStatus = static_cast<StatusService::DeviceStatus>(value.at(0));
649 status.batteryVoltage = qFromLittleEndian<float>(value.mid(1,4).constData());
650 if (value.size() >= 6) { // Battery Status added to Pokit API docs v1.00.
651 status.batteryStatus = static_cast<StatusService::BatteryStatus>(value.at(5));
652 }
653 if (value.size() >= 7) { // Switch Position - as yet, undocumented by Pokit Innovations.
654 status.switchPosition = static_cast<StatusService::SwitchPosition>(value.at(6));
655 }
656 if (value.size() >= 8) { // Charging Status - as yet, undocumented by Pokit Innovations.
657 status.chargingStatus = static_cast<StatusService::ChargingStatus>(value.at(7));
658 }
659 qCDebug(lc).noquote() << tr("Device status: %1 (%2)")
660 .arg((quint8)status.deviceStatus).arg(StatusService::toString(status.deviceStatus));
661 qCDebug(lc).noquote() << tr("Battery voltage: %1 volts").arg(status.batteryVoltage);
662 qCDebug(lc).noquote() << tr("Battery status: %1 (%2)")
663 .arg((quint8)status.batteryStatus).arg(StatusService::toString(status.batteryStatus));
664 if (status.switchPosition) {
665 qCDebug(lc).noquote() << tr("Switch position: %1 (%2)")
666 .arg((quint8)*status.switchPosition).arg(StatusService::toString(*status.switchPosition));
667 }
668 if (status.chargingStatus) {
669 qCDebug(lc).noquote() << tr("Charging status: %1 (%2)")
670 .arg((quint8)*status.chargingStatus).arg(StatusService::toString(*status.chargingStatus));
671 }
672 return status;
673}
674
675/*!
676 * Parses the torch status \a value, and returns the corresponding TorchStatus.
677 */
678std::optional<StatusService::TorchStatus> StatusServicePrivate::parseTorchStatus(const QByteArray &value)
679{
680 if (!checkSize(QLatin1String("Torch"), value, 1, 1)) {
681 return std::nullopt;
682 }
683
684 const StatusService::TorchStatus status = static_cast<StatusService::TorchStatus>(value.at(0));
685 qCDebug(lc).noquote() << tr("Torch status: %1 (%2)").arg((quint8)status).arg(StatusService::toString(status));
686 return status;
687}
688
689/*!
690 * Parses the button press \a value, and returns the corresponding ButtonStatus.
691 */
692std::optional<StatusService::ButtonStatus> StatusServicePrivate::parseButtonPress(const QByteArray &value)
693{
694 if (!checkSize(QLatin1String("Torch"), value, 2, 2)) {
695 return std::nullopt;
696 }
697
698 /*!
699 * \pokitApi The button event is the second byte, but no idea what the first byte is. In all examples
700 * I've see it's always `0x02`. It appears that the Pokit Android app only ever looks at `bytes[1]`.
701 *
702 * \pokitApi Note, we can actually write to the Button Press characteristic too. If we do, then whatever
703 * we set as the first byte persists, and (unsurprisingly) the second byte reverts to the current
704 * button state. So still no idea what that first byte is for.
705 */
706
707 const StatusService::ButtonStatus status = static_cast<StatusService::ButtonStatus>(value.at(1));
708 qCDebug(lc).noquote() << tr("Button: %1 (%2)").arg((quint8)status).arg(StatusService::toString(status));
709 return status;
710}
711
712/*!
713 * Handles `QLowEnergyController::serviceDiscovered` events.
714 *
715 * Here we override the base implementation to detect if we're looking at a Pokit Meter, or Pokit
716 * Pro device, as the two devices have very slightly different Status Service UUIDs.
717 */
719{
720 if (newService == StatusService::ServiceUuids::pokitMeter) {
721 qCDebug(lc).noquote() << tr("Found Status Service for a Pokit Meter device.");
723 } else if (newService == StatusService::ServiceUuids::pokitPro) {
724 qCDebug(lc).noquote() << tr("Found Status Service for a Pokit Pro device.");
726 }
728}
729
730/*!
731 * Implements AbstractPokitServicePrivate::characteristicRead to parse \a value, then emit a
732 * specialised signal, for each supported \a characteristic.
733 */
735 const QByteArray &value)
736{
738
739 Q_Q(StatusService);
741 Q_EMIT q->deviceCharacteristicsRead(parseDeviceCharacteristics(value));
742 return;
743 }
744
745 if (characteristic.uuid() == StatusService::CharacteristicUuids::status) {
746 Q_EMIT q->deviceStatusRead(parseStatus(value));
747 return;
748 }
749
750 if (characteristic.uuid() == StatusService::CharacteristicUuids::name) {
751 const QString deviceName = QString::fromUtf8(value);
752 qCDebug(lc).noquote() << tr(R"(Device name: "%1")").arg(deviceName);
753 Q_EMIT q->deviceNameRead(deviceName);
754 return;
755 }
756
757 if (characteristic.uuid() == StatusService::CharacteristicUuids::flashLed) {
758 qCWarning(lc).noquote() << tr("Flash LED characteristic is write-only, but somehow read")
759 << serviceUuid << characteristic.name() << characteristic.uuid();
760 return;
761 }
762
763 if (characteristic.uuid() == StatusService::CharacteristicUuids::torch) {
764 if (!checkSize(QLatin1String("Torch"), value, 1, 1)) {
765 return;
766 }
767 const StatusService::TorchStatus status = static_cast<StatusService::TorchStatus>(value.at(0));
768 qCDebug(lc).noquote() << tr("Torch status: %1 (%2)").arg((quint8)status).arg(StatusService::toString(status));
769 Q_EMIT q->torchStatusRead(status);
770 return;
771 }
772
774 if (!checkSize(QLatin1String("Torch"), value, 2, 2)) {
775 return;
776 }
777 const StatusService::ButtonStatus status = static_cast<StatusService::ButtonStatus>(value.at(1));
778 qCDebug(lc).noquote() << tr("Button status: %1 (%2)").arg((quint8)status).arg(StatusService::toString(status));
779 Q_EMIT q->buttonPressRead(value.at(0), status);
780 return;
781 }
782
783 qCWarning(lc).noquote() << tr("Unknown characteristic read for Status service")
784 << serviceUuid << characteristic.name() << characteristic.uuid();
785}
786
787/*!
788 * Implements AbstractPokitServicePrivate::characteristicWritten to parse \a newValue, then emit a
789 * specialised signal, for each supported \a characteristic.
790 */
792 const QByteArray &newValue)
793{
795
796 Q_Q(StatusService);
798 qCWarning(lc).noquote() << tr("Device Characteristics is read-only, but somehow written")
799 << serviceUuid << characteristic.name() << characteristic.uuid();
800 return;
801 }
802
803 if (characteristic.uuid() == StatusService::CharacteristicUuids::status) {
804 qCWarning(lc).noquote() << tr("Status characteristic is read-only, but somehow written")
805 << serviceUuid << characteristic.name() << characteristic.uuid();
806 return;
807 }
808
809 if (characteristic.uuid() == StatusService::CharacteristicUuids::name) {
810 Q_EMIT q->deviceNameWritten();
811 return;
812 }
813
814 if (characteristic.uuid() == StatusService::CharacteristicUuids::flashLed) {
815 Q_EMIT q->deviceLedFlashed();
816 return;
817 }
818
819 if (characteristic.uuid() == StatusService::CharacteristicUuids::torch) {
820 Q_EMIT q->torchStatusWritten();
821 return;
822 }
823
824 qCWarning(lc).noquote() << tr("Unknown characteristic written for Status service")
825 << serviceUuid << characteristic.name() << characteristic.uuid();
826}
827
828/// \endcond
The AbstractPokitServicePrivate class provides private implementation for AbstractPokitService.
QBluetoothUuid serviceUuid
UUIDs for service.
virtual void serviceDiscovered(const QBluetoothUuid &newService)
Handles QLowEnergyController::serviceDiscovered events.
virtual void characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &value)
Handles QLowEnergyService::characteristicRead events.
virtual void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
Handles QLowEnergyService::characteristicWritten events.
static bool checkSize(const QString &label, const QByteArray &data, const int minSize, const int maxSize=-1, const bool failOnMax=false)
Returns false if data is smaller than minSize, otherwise returns failOnMax if data is bigger than max...
The AbstractPokitService class provides a common base for Pokit services classes.
QLowEnergyService * service()
Returns a non-const pointer to the internal service object, if any.
The StatusServicePrivate class provides private implementation for StatusService.
void characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) override
Implements AbstractPokitServicePrivate::characteristicRead to parse value, then emit a specialised si...
StatusServicePrivate(QLowEnergyController *controller, StatusService *const q)
static StatusService::Status parseStatus(const QByteArray &value)
Parses the Status value into a Status struct.
void serviceDiscovered(const QBluetoothUuid &newService) override
Handles QLowEnergyController::serviceDiscovered events.
static StatusService::DeviceCharacteristics parseDeviceCharacteristics(const QByteArray &value)
Parses the Device Characteristics value into a DeviceCharacteristics struct.
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) override
Implements AbstractPokitServicePrivate::characteristicWritten to parse newValue, then emit a speciali...
static std::optional< StatusService::ButtonStatus > parseButtonPress(const QByteArray &value)
Parses the button press value, and returns the corresponding ButtonStatus.
static std::optional< StatusService::TorchStatus > parseTorchStatus(const QByteArray &value)
Parses the torch status value, and returns the corresponding TorchStatus.
The StatusService class accesses the Pokit Status service of Pokit devices.
DeviceCharacteristics deviceCharacteristics() const
Returns the most recent value of the Status service's Device Characteristics characteristic.
bool enableButtonPressedNotifications()
Enables client-side notifications of button presses.
bool readStatusCharacteristic()
Read the Status service's Status characteristic.
StatusService(QLowEnergyController *const pokitDevice, QObject *parent=nullptr)
Constructs a new Pokit service with parent.
QString deviceName() const
Returns the most recent value of the Status services's Device Name characteristic.
ButtonStatus
Values supported by the second byte of the attribute of the (undocumented) Button Press characteristi...
@ Pressed
Button was pressed.
@ Released
Button was released.
@ Held
Button was held down (for typically 1,500ms).
bool readNameCharacteristic()
Read the Status service's Name characteristic.
bool disableStatusNotifications()
Disables client-side notifications of device status changes.
BatteryStatus
Values supported by the Battery Status attribute of the Status characteristic.
@ Low
Low (replace battery).
bool readButtonPressCharacteristic()
Read the Status service's (undocumented) Button Press characteristic.
bool setTorchStatus(const TorchStatus status)
Set the Pokit device's torch to status.
bool flashLed()
Flash the Pokit device's LED.
DeviceStatus
Values supported by the Status attribute of the Status characteristic.
@ MultimeterContinuity
Multimeter is measuring continuity.
@ MultimeterAcCurrent
Multimeter is measuring AC current.
@ LoggerModeSampling
Data Logger is sampling.
@ MultimeterDcVoltage
Multimeter is measuring DC voltage.
@ DsoModeSampling
DSO is sampling.
@ MultimeterDcCurrent
Multimeter is measuring DC current.
@ MultimeterTemperature
Multimeter is measuring temperature.
@ MultimeterResistance
Multimeter is measuring resistance.
@ MultimeterDiode
Multimeter is measuring diode.
@ MultimeterAcVoltage
Multimeter is measuring AC voltage.
bool enableStatusNotifications()
Enables client-side notifications of device status changes.
~StatusService() override
Destroys this StatusService object.
std::optional< ButtonStatus > buttonPress() const
Returns the most recent value of the Status services's Button Press characteristic.
bool enableTorchStatusNotifications()
Enables client-side notifications of torch status changes.
SwitchPosition
Values supported by the (undocumented) Switch Position attribute of the Status characteristic.
@ HighCurrent
Device is switched to High Current position.
@ Voltage
Device is switched to Voltage position.
@ MultiMode
Device is switched to Resistance / Low Current / Capacitance / Diode position.
bool disableTorchStatusNotifications()
Disables client-side notifications of torch status changes.
static QString toString(const StatusService::DeviceStatus &status)
Returns a string version of the status enum label.
bool disableButtonPressedNotifications()
Disables client-side notifications of button presses.
TorchStatus
Values supported by the single byte of the attribute of the (undocumented) Torch characteristic.
std::optional< TorchStatus > torchStatus() const
Returns the most recent value of the Status services's Torch characteristic.
bool readCharacteristics() override
Read all characteristics.
bool setDeviceName(const QString &name)
Set's the Pokit device's name to name.
bool readDeviceCharacteristics()
Read the Status service's Device Characteristics characteristic.
Status status() const
Returns the most recent value of the Status service's Status characteristic.
ChargingStatus
Values supported by the (undocumented) Charging Statue attribute of the Status characteristic.
@ Discharging
Battery is dischargin.
@ Charged
Battery is fully charged.
@ Charging
Battery is being charged.
bool readTorchCharacteristic()
Read the Status service's (undocumented) Torch characteristic.
char at(int i) const const
const char * constData() const const
int length() const const
QByteArray mid(int pos, int len) const const
int size() const const
QByteArray toHex() const const
bool isValid() const const
QString name() const const
QBluetoothUuid uuid() const const
QByteArray value() const const
QLowEnergyCharacteristic characteristic(const QBluetoothUuid &uuid) const const
Q_EMITQ_EMIT
QString tr(const char *sourceText, const char *disambiguation, int n)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString fromUtf8(const char *str, int size)
QByteArray toUtf8() const const
Declares the StatusService class.
Declares the StatusServicePrivate class.
static const QBluetoothUuid name
UUID of the Pokit Status service's Device Name characterstic.
static const QBluetoothUuid torch
UUID of the Pokit Status service's (undocumented) Torch characterstic.
static const QBluetoothUuid deviceCharacteristics
UUID of the Pokit Status service's Device Characteristics characterstic.
static const QBluetoothUuid buttonPress
UUID of the Pokit Status service's (undocumented) Button Press characterstic.
static const QBluetoothUuid flashLed
UUID of the Pokit Status service's Flash LED characterstic.
static const QBluetoothUuid status
UUID of the Pokit Status service's Status characterstic.
Attributes included in the Device Characteristics characterstic.
static const QBluetoothUuid pokitPro
UUID of the Pokit Pro's Pokit Status service.
static const QBluetoothUuid pokitMeter
UUID of the Pokit Meter's Pokit Status service.
Attributes included in the Status characterstic.
DeviceStatus deviceStatus
Current Pokit device status.