Dokit
Internal development documentation
Loading...
Searching...
No Matches
statusservice.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2022-2024 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
143{
144 const bool r1 = readDeviceCharacteristics();
145 const bool r2 = readStatusCharacteristic();
146 const bool r3 = readNameCharacteristic();
147 const bool r4 = ((service() != nullptr) && (service()->characteristic(CharacteristicUuids::torch).isValid()))
148 ? readTorchCharacteristic() : true;
149 const bool r5 = ((service() != nullptr) && (service()->characteristic(CharacteristicUuids::buttonPress).isValid()))
151 return (r1 && r2 && r3 && r4 && r5);
152}
153
154/*!
155 * Read the `Status` service's `Device Characteristics` characteristic.
156 *
157 * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
158 * underlying controller it not yet connected to the Pokit device, or the device's services have
159 * not yet been discovered).
160 *
161 * Emits deviceCharacteristicsRead() if/when the characteristic has been read successfully.
162 */
168
169/*!
170 * Read the `Status` service's `Status` characteristic.
171 *
172 * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
173 * underlying controller it not yet connected to the Pokit device, or the device's services have
174 * not yet been discovered).
175 *
176 * Emits deviceStatusRead() if/when the characteristic has been read successfully.
177 */
179{
180 Q_D(StatusService);
181 return d->readCharacteristic(CharacteristicUuids::status);
182}
183
184/*!
185 * Read the `Status` service's `Name` characteristic.
186 *
187 * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
188 * underlying controller it not yet connected to the Pokit device, or the device's services have
189 * not yet been discovered).
190 *
191 * Emits deviceNameRead() if/when the characteristic has been read successfully.
192 */
194{
195 Q_D(StatusService);
196 return d->readCharacteristic(CharacteristicUuids::name);
197}
198
199/*!
200 * Read the `Status` service's (undocumented) `Torch` characteristic.
201 *
202 * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
203 * underlying controller it not yet connected to the Pokit device, or the device's services have
204 * not yet been discovered).
205 *
206 * Emits torchStatusRead() if/when the characteristic has been read successfully.
207 */
209{
210 Q_D(StatusService);
211 return d->readCharacteristic(CharacteristicUuids::torch);
212}
213
214/*!
215 * Read the `Status` service's (undocumented) `Button Press` characteristic.
216 *
217 * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
218 * underlying controller it not yet connected to the Pokit device, or the device's services have
219 * not yet been discovered).
220 *
221 * Emits buttonPressRead() if/when the characteristic has been read successfully.
222 */
224{
225 Q_D(StatusService);
226 return d->readCharacteristic(CharacteristicUuids::buttonPress);
227}
228
229/*!
230 * Returns the most recent value of the `Status` service's `Device Characteristics` characteristic.
231 *
232 * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
233 * currently available (ie the serviceDetailsDiscovered signal has not been emitted yet), then a
234 * null result is returned, which can be checked via the returned
235 * DeviceCharacteristics::firmwareVersion, like:
236 *
237 * ```
238 * const DeviceCharacteristics characteristics = service->deviceCharacteristics();
239 * if (!characteristics.firmwareVersion.isNull()) {
240 * ...
241 * }
242 * ```
243 */
253
254/*!
255 * Returns the most recent value of the `Status` service's `Status` characteristic.
256 *
257 * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
258 * currently available (ie the serviceDetailsDiscovered signal has not been emitted yet), then the
259 * returned StatusService::Status::batteryLevel member will be a quiet NaN, which can be checked
260 * like:
261 *
262 * ```
263 * const StatusService::Status status = statusService->status();
264 * if (qIsNaN(status.batteryVoltage)) {
265 * // Handle failure.
266 * }
267 * ```
268 *
269 * Not all Pokit devices support the Status::batteryStatus member, in which case the member will be
270 * initilialised to the maximum value supported by the underlying type (ie `255`) to indicate "not set"
271 */
273{
274 Q_D(const StatusService);
275 const QLowEnergyCharacteristic characteristic =
276 d->getCharacteristic(CharacteristicUuids::status);
277 return (characteristic.isValid()) ? StatusServicePrivate::parseStatus(characteristic.value())
278 : StatusService::Status{ DeviceStatus::Idle, std::numeric_limits<float>::quiet_NaN(),
279 BatteryStatus::Low, std::nullopt, std::nullopt };
280}
281
282/*!
283 * Enables client-side notifications of device status changes.
284 *
285 * This is an alternative to manually requesting individual reads via readStatusCharacteristic().
286 *
287 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
288 *
289 * Successfully read values (if any) will be emitted via the deviceStatusRead() signal.
290 */
292{
293 Q_D(StatusService);
294 return d->enableCharacteristicNotificatons(CharacteristicUuids::status);
295}
296
297/*!
298 * Disables client-side notifications of device status changes.
299 *
300 * Instantaneous status can still be fetched by readStatusCharacteristic().
301 *
302 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
303 */
305{
306 Q_D(StatusService);
307 return d->disableCharacteristicNotificatons(CharacteristicUuids::status);
308}
309
310/*!
311 * Returns the most recent value of the `Status` services's `Device Name` characteristic.
312 *
313 * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
314 * currently available (ie the serviceDetailsDiscovered signal has not been emitted yet), then a
315 * null QString is returned.
316 */
318{
319 Q_D(const StatusService);
320 const QLowEnergyCharacteristic characteristic =
321 d->getCharacteristic(CharacteristicUuids::name);
322 return (characteristic.isValid()) ? QString::fromUtf8(characteristic.value()) : QString();
323}
324
325/*!
326 * Set's the Pokit device's name to \a name.
327 *
328 * Returns `true` if the write request was successfully queued, `false` otherwise.
329 *
330 * Emits deviceNameWritten() if/when the \a name has been set.
331 */
333{
334 Q_D(const StatusService);
335 const QLowEnergyCharacteristic characteristic =
336 d->getCharacteristic(CharacteristicUuids::name);
337 if (!characteristic.isValid()) {
338 return false;
339 }
340
341 const QByteArray value = name.toUtf8();
342 if (value.length() > 11) {
343 qCWarning(d->lc).noquote() << tr(R"(Device name "%1" is too long (%2 > 11 bytes): 0x3)")
344 .arg(name).arg(value.length()).arg(QLatin1String(value.toHex()));
345 return false;
346 }
347
348 d->service->writeCharacteristic(characteristic, value);
349 return (d->service->error() != QLowEnergyService::ServiceError::CharacteristicWriteError);
350}
351
352/*!
353 * Flash the Pokit device's LED.
354 *
355 * Returns `true` if the flash request was successfully queued, `false` otherwise.
356 *
357 * Emits deviceLedFlashed() if/when the LED has flashed successfully.
358 *
359 * \note This operation is only supported by Pokit Meter devices. Pokit Pro devices will report an
360 * Bluetooth ATT error `0x80`.
361 *
362 * \cond internal
363 * \pokitApi The Android app can turn Pokit Pro LEDs on/off. Perhaps that is handled by an
364 * undocumented use of this characteristic. Or perhaps its via some other service.
365 * \endcond
366 */
368{
369 Q_D(const StatusService);
370 const QLowEnergyCharacteristic characteristic =
371 d->getCharacteristic(CharacteristicUuids::flashLed);
372 if (!characteristic.isValid()) {
373 return false;
374 }
375
376 // The Flash LED characeristic is write-only, and takes a single uint8 "LED" parameter, which
377 // must always be 1. Presumably this is an index for which LED to flash, but the Pokit API docs
378 // say that "any value other than 1 will be ignored", which makes sense given that all current
379 // Pokit devices have only one LED.
380 const QByteArray value(1, '\x01');
381 d->service->writeCharacteristic(characteristic, value);
382 return (d->service->error() != QLowEnergyService::ServiceError::CharacteristicWriteError);
383}
384
385/*!
386 * Returns the most recent value of the `Status` services's `Torch` characteristic.
387 *
388 * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
389 * currently available (eg if the device does not support the Torch characteristic), then `nullopt`
390 * is returned.
391 */
392std::optional<StatusService::TorchStatus> StatusService::torchStatus() const
393{
394 Q_D(const StatusService);
395 const QLowEnergyCharacteristic characteristic = d->getCharacteristic(CharacteristicUuids::torch);
396 return (characteristic.isValid()) ? StatusServicePrivate::parseTorchStatus(characteristic.value()) : std::nullopt;
397}
398
399/*!
400 * Set the Pokit device's torch to \a status.
401 *
402 * Returns `true` if the request was successfully queued, `false` otherwise.
403 *
404 * Emits torchStatusWritten() if/when the LED has flashed successfully.
405 *
406 * \note This operation is only supported by Pokit Pro devices, and not Pokit Meter devices.
407 */
409{
410 Q_D(const StatusService);
411 const QLowEnergyCharacteristic characteristic = d->getCharacteristic(CharacteristicUuids::torch);
412 if (!characteristic.isValid()) {
413 return false;
414 }
415
416 const QByteArray value(1, static_cast<char>(status));
417 d->service->writeCharacteristic(characteristic, value);
418 return (d->service->error() != QLowEnergyService::ServiceError::CharacteristicWriteError);
419}
420
421/*!
422 * Enables client-side notifications of torch status changes.
423 *
424 * This is an alternative to manually requesting individual reads via readTorchCharacteristic().
425 *
426 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
427 *
428 * Successfully read values (if any) will be emitted via the torchStatusRead() signal.
429 */
431{
432 Q_D(StatusService);
433 return d->enableCharacteristicNotificatons(CharacteristicUuids::torch);
434}
435
436/*!
437 * Disables client-side notifications of torch status changes.
438 *
439 * Instantaneous torch status can still be fetched by readTorchCharacteristic().
440 *
441 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
442 */
444{
445 Q_D(StatusService);
446 return d->disableCharacteristicNotificatons(CharacteristicUuids::torch);
447}
448
449/*!
450 * Enables client-side notifications of button presses.
451 *
452 * This is an alternative to manually requesting individual reads via readButtonPressCharacteristic().
453 *
454 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
455 *
456 * Successfully read values (if any) will be emitted via the torchStatusRead() signal.
457 */
459{
460 Q_D(StatusService);
461 return d->enableCharacteristicNotificatons(CharacteristicUuids::buttonPress);
462}
463
464/*!
465 * Disables client-side notifications of button presses.
466 *
467 * Instantaneous button press statussed can still be fetched by readButtonPressCharacteristic().
468 *
469 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
470 */
472{
473 Q_D(StatusService);
474 return d->disableCharacteristicNotificatons(CharacteristicUuids::buttonPress);
475}
476
477/*!
478 * Returns the most recent value of the `Status` services's `Button Press` characteristic.
479 *
480 * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
481 * currently available (eg if the device does not support the Torch characteristic), then `nullopt`
482 * is returned.
483 */
484std::optional<StatusService::ButtonStatus> StatusService::buttonPress() const
485{
486 Q_D(const StatusService);
487 const QLowEnergyCharacteristic characteristic = d->getCharacteristic(CharacteristicUuids::buttonPress);
488 return (characteristic.isValid()) ? StatusServicePrivate::parseButtonPress(characteristic.value()) : std::nullopt;
489}
490
491/*!
492 * \fn StatusService::deviceCharacteristicsRead
493 *
494 * This signal is emitted when the `Device Characteristics` characteristic has been read
495 * successfully.
496 *
497 * \see readDeviceCharacteristics
498 */
499
500/*!
501 * \fn StatusService::deviceNameRead
502 *
503 * This signal is emitted when the `Device Name` characteristic has been read successfully.
504 *
505 * \see readDeviceName
506 */
507
508/*!
509 * \fn StatusService::deviceNameWritten
510 *
511 * This signal is emitted when the `Device Name` characteristic has been written successfully.
512 *
513 * \see setDeviceName
514 */
515
516/*!
517 * \fn StatusService::deviceStatusRead
518 *
519 * This signal is emitted when the `Status` characteristic has been read successfully.
520 *
521 * \see readDeviceStatus
522 */
523
524/*!
525 * \fn StatusService::deviceLedFlashed
526 *
527 * This signal is emitted when device's LED has flashed in response to a write of the `Flash LED`
528 * characteristic.
529 */
530
531/*!
532 * \fn StatusService::torchStatusRead
533 *
534 * This signal is emitted when the `Torch` characteristic has been read successfully.
535 *
536 * \see setTorchStatus
537 */
538
539/*!
540 * \fn StatusService::torchStatusWritten
541 *
542 * This signal is emitted when the `Torch` characteristic has been written successfully.
543 *
544 * \see readTorchCharacteristic
545 */
546
547/*!
548 * \fn StatusService::buttonPressRead
549 *
550 * This signal is emitted when the `Button Press` characteristic has been read successfully.
551 *
552 * \see readButtonPressCharacteristic
553 */
554
555/*!
556 * \cond internal
557 * \class StatusServicePrivate
558 *
559 * The StatusServicePrivate class provides private implementation for StatusService.
560 */
561
562/*!
563 * \internal
564 * Constructs a new StatusServicePrivate object with public implementation \a q.
565 */
572
573/*!
574 * Parses the `Device Characteristics` \a value into a DeviceCharacteristics struct.
575 */
577 const QByteArray &value)
578{
580 QVersionNumber(), 0, 0, 0, 0, 0, 0, QBluetoothAddress()
581 };
582 Q_ASSERT(characteristics.firmwareVersion.isNull()); // How we indicate failure.
583
584 if (!checkSize(QLatin1String("Device Characteristics"), value, 20, 20)) {
585 return characteristics;
586 }
587
588 characteristics.firmwareVersion = QVersionNumber(
589 qFromLittleEndian<quint8 >(value.mid(0,1).constData()),
590 qFromLittleEndian<quint8 >(value.mid(1,1).constData()));
591 characteristics.maximumVoltage = qFromLittleEndian<quint16>(value.mid(2,2).constData());
592 characteristics.maximumCurrent = qFromLittleEndian<quint16>(value.mid(4,2).constData());
593 characteristics.maximumResistance = qFromLittleEndian<quint16>(value.mid(6,2).constData());
594 characteristics.maximumSamplingRate = qFromLittleEndian<quint16>(value.mid(8,2).constData());
595 characteristics.samplingBufferSize = qFromLittleEndian<quint16>(value.mid(10,2).constData());
596 characteristics.capabilityMask = qFromLittleEndian<quint16>(value.mid(12,2).constData());
597 characteristics.macAddress = QBluetoothAddress(qFromBigEndian<quint64>
598 ((QByteArray(2, '\0') + value.mid(14,6)).constData()));
599
600 qCDebug(lc).noquote() << tr("Firmware version: ") << characteristics.firmwareVersion;
601 qCDebug(lc).noquote() << tr("Maximum voltage: ") << characteristics.maximumVoltage;
602 qCDebug(lc).noquote() << tr("Maximum current: ") << characteristics.maximumCurrent;
603 qCDebug(lc).noquote() << tr("Maximum resistance: ") << characteristics.maximumResistance;
604 qCDebug(lc).noquote() << tr("Maximum sampling rate:") << characteristics.maximumSamplingRate;
605 qCDebug(lc).noquote() << tr("Sampling buffer size: ") << characteristics.samplingBufferSize;
606 qCDebug(lc).noquote() << tr("Capability mask: ") << characteristics.capabilityMask;
607 qCDebug(lc).noquote() << tr("MAC address: ") << characteristics.macAddress;
608
609 Q_ASSERT(!characteristics.firmwareVersion.isNull()); // How we indicate success.
610 return characteristics;
611}
612
613/*!
614 * Parses the `Status` \a value into a Status struct. Note, not all Pokit devices support all members
615 * in Status. Specifically, the batteryStatus member is not usually set by Pokit Meter devices, so
616 * will be an invlalid BatteryStatus enum value (`255`) in that case.
617 */
619{
621 static_cast<StatusService::DeviceStatus>
622 (std::numeric_limits<std::underlying_type_t<StatusService::DeviceStatus>>::max()),
623 std::numeric_limits<float>::quiet_NaN(),
625 (std::numeric_limits<std::underlying_type_t<StatusService::BatteryStatus>>::max()),
626 std::nullopt, std::nullopt,
627 };
628
629 /*!
630 * \pokitApi Pokit API 0.02 says the `Status` characteristic is 5 bytes. API 1.00 then added an
631 * additional byte for `Battery Status`, for 6 bytes in total. However, Pokit Pro devices return
632 * 8 bytes here. It appears that the first of those 2 extra bytes (ie the 7th byte) is used to
633 * indicate the physical switch position, while the other extra byte (ie the 8th byte) indicates
634 * the device's current charging status.
635 */
636
637 if (!checkSize(QLatin1String("Status"), value, 5, 8)) {
638 return status;
639 }
640
641 status.deviceStatus = static_cast<StatusService::DeviceStatus>(value.at(0));
642 status.batteryVoltage = qFromLittleEndian<float>(value.mid(1,4).constData());
643 if (value.size() >= 6) { // Battery Status added to Pokit API docs v1.00.
644 status.batteryStatus = static_cast<StatusService::BatteryStatus>(value.at(5));
645 }
646 if (value.size() >= 7) { // Switch Position - as yet, undocumented by Pokit Innovations.
647 status.switchPosition = static_cast<StatusService::SwitchPosition>(value.at(6));
648 }
649 if (value.size() >= 8) { // Charging Status - as yet, undocumented by Pokit Innovations.
650 status.chargingStatus = static_cast<StatusService::ChargingStatus>(value.at(7));
651 }
652 qCDebug(lc).noquote() << tr("Device status: %1 (%2)")
653 .arg((quint8)status.deviceStatus).arg(StatusService::toString(status.deviceStatus));
654 qCDebug(lc).noquote() << tr("Battery voltage: %1 volts").arg(status.batteryVoltage);
655 qCDebug(lc).noquote() << tr("Battery status: %1 (%2)")
656 .arg((quint8)status.batteryStatus).arg(StatusService::toString(status.batteryStatus));
657 if (status.switchPosition) {
658 qCDebug(lc).noquote() << tr("Switch position: %1 (%2)")
659 .arg((quint8)*status.switchPosition).arg(StatusService::toString(*status.switchPosition));
660 }
661 if (status.chargingStatus) {
662 qCDebug(lc).noquote() << tr("Charging status: %1 (%2)")
663 .arg((quint8)*status.chargingStatus).arg(StatusService::toString(*status.chargingStatus));
664 }
665 return status;
666}
667
668/*!
669 * Parses the torch status \a value, and returns the corresponding TorchStatus.
670 */
671std::optional<StatusService::TorchStatus> StatusServicePrivate::parseTorchStatus(const QByteArray &value)
672{
673 if (!checkSize(QLatin1String("Torch"), value, 1, 1)) {
674 return std::nullopt;
675 }
676
677 const StatusService::TorchStatus status = static_cast<StatusService::TorchStatus>(value.at(0));
678 qCDebug(lc).noquote() << tr("Torch status: %1 (%2)").arg((quint8)status).arg(StatusService::toString(status));
679 return status;
680}
681
682/*!
683 * Parses the button press \a value, and returns the corresponding ButtonStatus.
684 */
685std::optional<StatusService::ButtonStatus> StatusServicePrivate::parseButtonPress(const QByteArray &value)
686{
687 if (!checkSize(QLatin1String("Torch"), value, 2, 2)) {
688 return std::nullopt;
689 }
690
691 /*!
692 * \pokitApi The button event is the second byte, but no idea what the first byte is. In all examples
693 * I've see it's always `0x02`. It appears that the Pokit Android app only ever looks at `bytes[1]`.
694 *
695 * \pokitApi Note, we can actually write to the Button Press characteristic too. If we do, then whatever
696 * we set as the first byte persists, and (unsurprisingly) the second byte reverts to the current
697 * button state. So still no idea what that first byte is for.
698 */
699
700 const StatusService::ButtonStatus status = static_cast<StatusService::ButtonStatus>(value.at(1));
701 qCDebug(lc).noquote() << tr("Button: %1 (%2)").arg((quint8)status).arg(StatusService::toString(status));
702 return status;
703}
704
705/*!
706 * Handles `QLowEnergyController::serviceDiscovered` events.
707 *
708 * Here we override the base implementation to detect if we're looking at a Pokit Meter, or Pokit
709 * Pro device, as the two devices have very slightly different Status Service UUIDs.
710 */
712{
713 if (newService == StatusService::ServiceUuids::pokitMeter) {
714 qCDebug(lc).noquote() << tr("Found Status Service for a Pokit Meter device.");
716 } else if (newService == StatusService::ServiceUuids::pokitPro) {
717 qCDebug(lc).noquote() << tr("Found Status Service for a Pokit Pro device.");
719 }
721}
722
723/*!
724 * Implements AbstractPokitServicePrivate::characteristicRead to parse \a value, then emit a
725 * specialised signal, for each supported \a characteristic.
726 */
728 const QByteArray &value)
729{
731
732 Q_Q(StatusService);
734 Q_EMIT q->deviceCharacteristicsRead(parseDeviceCharacteristics(value));
735 return;
736 }
737
738 if (characteristic.uuid() == StatusService::CharacteristicUuids::status) {
739 Q_EMIT q->deviceStatusRead(parseStatus(value));
740 return;
741 }
742
743 if (characteristic.uuid() == StatusService::CharacteristicUuids::name) {
744 const QString deviceName = QString::fromUtf8(value);
745 qCDebug(lc).noquote() << tr(R"(Device name: "%1")").arg(deviceName);
746 Q_EMIT q->deviceNameRead(deviceName);
747 return;
748 }
749
750 if (characteristic.uuid() == StatusService::CharacteristicUuids::flashLed) {
751 qCWarning(lc).noquote() << tr("Flash LED characteristic is write-only, but somehow read")
752 << serviceUuid << characteristic.name() << characteristic.uuid();
753 return;
754 }
755
756 if (characteristic.uuid() == StatusService::CharacteristicUuids::torch) {
757 if (!checkSize(QLatin1String("Torch"), value, 1, 1)) {
758 return;
759 }
760 const StatusService::TorchStatus status = static_cast<StatusService::TorchStatus>(value.at(0));
761 qCDebug(lc).noquote() << tr("Torch status: %1 (%2)").arg((quint8)status).arg(StatusService::toString(status));
762 Q_EMIT q->torchStatusRead(status);
763 return;
764 }
765
767 if (!checkSize(QLatin1String("Torch"), value, 2, 2)) {
768 return;
769 }
770 const StatusService::ButtonStatus status = static_cast<StatusService::ButtonStatus>(value.at(1));
771 qCDebug(lc).noquote() << tr("Button status: %1 (%2)").arg((quint8)status).arg(StatusService::toString(status));
772 Q_EMIT q->buttonPressRead(value.at(0), status);
773 return;
774 }
775
776 qCWarning(lc).noquote() << tr("Unknown characteristic read for Status service")
777 << serviceUuid << characteristic.name() << characteristic.uuid();
778}
779
780/*!
781 * Implements AbstractPokitServicePrivate::characteristicWritten to parse \a newValue, then emit a
782 * specialised signal, for each supported \a characteristic.
783 */
785 const QByteArray &newValue)
786{
788
789 Q_Q(StatusService);
791 qCWarning(lc).noquote() << tr("Device Characteristics is read-only, but somehow written")
792 << serviceUuid << characteristic.name() << characteristic.uuid();
793 return;
794 }
795
796 if (characteristic.uuid() == StatusService::CharacteristicUuids::status) {
797 qCWarning(lc).noquote() << tr("Status characteristic is read-only, but somehow written")
798 << serviceUuid << characteristic.name() << characteristic.uuid();
799 return;
800 }
801
802 if (characteristic.uuid() == StatusService::CharacteristicUuids::name) {
803 Q_EMIT q->deviceNameWritten();
804 return;
805 }
806
807 if (characteristic.uuid() == StatusService::CharacteristicUuids::flashLed) {
808 Q_EMIT q->deviceLedFlashed();
809 return;
810 }
811
812 if (characteristic.uuid() == StatusService::CharacteristicUuids::torch) {
813 Q_EMIT q->torchStatusWritten();
814 return;
815 }
816
817 qCWarning(lc).noquote() << tr("Unknown characteristic written for Status service")
818 << serviceUuid << characteristic.name() << characteristic.uuid();
819}
820
821/// \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.
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.