Line data Source code
1 : // SPDX-FileCopyrightText: 2022-2023 Paul Colby <git@colby.id.au>
2 : // SPDX-License-Identifier: LGPL-3.0-or-later
3 :
4 : /*!
5 : * \file
6 : * Defines the StatusService and StatusServicePrivate classes.
7 : */
8 :
9 : #include <qtpokit/statusservice.h>
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 : * \struct StatusService::ServiceUuids
22 : *
23 : * UUIDs of the "Pokit Status" service.
24 : *
25 : * \cond internal
26 : * \pokitApi Pokit API 1.00 (and 0.02) states the Status Service UUID as
27 : * `57d3a771-267c-4394-8872-78223e92aec4` which is correct for the Pokit Meter, but Pokit Pro uses
28 : * `57d3a771-267c-4394-8872-78223e92aec5` instead, that is the last digit is a `5` not `4`.
29 : * \endcond
30 : */
31 :
32 : /// UUID of the Pokit Meter's `Pokit Status` service.
33 : const QBluetoothUuid StatusService::ServiceUuids::
34 : pokitMeter(QLatin1String("57d3a771-267c-4394-8872-78223e92aec4"));
35 :
36 : /// UUID of the Pokit Pro's `Pokit Status` service.
37 : const QBluetoothUuid StatusService::ServiceUuids::
38 : pokitPro(QLatin1String("57d3a771-267c-4394-8872-78223e92aec5"));
39 :
40 : /// \struct StatusService::CharacteristicUuids
41 : /// \brief Characteristics available via the `Pokit Status` service.
42 :
43 : /// UUID of the `Pokit Status` service's `Device Characteristics` characterstic.
44 : const QBluetoothUuid StatusService::CharacteristicUuids::
45 : deviceCharacteristics(QLatin1String("6974f5e5-0e54-45c3-97dd-29e4b5fb0849"));
46 :
47 : /// UUID of the `Pokit Status` service's `Status` characterstic.
48 : const QBluetoothUuid StatusService::CharacteristicUuids::
49 : status(QLatin1String("3dba36e1-6120-4706-8dfd-ed9c16e569b6"));
50 :
51 : /// UUID of the `Pokit Status` service's `Device Name` characterstic.
52 : const QBluetoothUuid StatusService::CharacteristicUuids::
53 : name(QLatin1String("7f0375de-077e-4555-8f78-800494509cc3"));
54 :
55 : /// UUID of the `Pokit Status` service's `Flash LED` characterstic.
56 : const QBluetoothUuid StatusService::CharacteristicUuids::
57 : flashLed(QLatin1String("ec9bb1f3-05a9-4277-8dd0-60a7896f0d6e"));
58 :
59 : /// \struct StatusService::DeviceCharacteristics
60 : /// \brief Attributes included in the `Device Characteristics` characterstic.
61 :
62 : /// \enum StatusService::DeviceStatus
63 : /// \brief Values supported by the `Status` attribute of the `Status` characteristic.
64 :
65 : /*!
66 : * Returns a string version of the \a status enum label.
67 : */
68 374 : QString StatusService::toString(const StatusService::DeviceStatus &status)
69 : {
70 374 : switch (status) {
71 30 : case DeviceStatus::Idle: return QLatin1String("Idle");
72 3 : case DeviceStatus::MultimeterDcVoltage: return QLatin1String("MultimeterDcVoltage");
73 3 : case DeviceStatus::MultimeterAcVoltage: return QLatin1String("MultimeterAcVoltage");
74 3 : case DeviceStatus::MultimeterDcCurrent: return QLatin1String("MultimeterDcCurrent");
75 3 : case DeviceStatus::MultimeterAcCurrent: return QLatin1String("MultimeterAcCurrent");
76 3 : case DeviceStatus::MultimeterResistance: return QLatin1String("MultimeterResistance");
77 3 : case DeviceStatus::MultimeterDiode: return QLatin1String("MultimeterDiode");
78 3 : case DeviceStatus::MultimeterContinuity: return QLatin1String("MultimeterContinuity");
79 3 : case DeviceStatus::MultimeterTemperature:return QLatin1String("MultimeterTemperature");
80 3 : case DeviceStatus::DsoModeSampling: return QLatin1String("DsoModeSampling");
81 3 : case DeviceStatus::LoggerModeSampling: return QLatin1String("LoggerModeSampling");
82 : }
83 : return QString();
84 : }
85 :
86 : /// \enum StatusService::BatteryStatus
87 : /// \brief Values supported by the `Battery Status` attribute of the `Status` characteristic.
88 :
89 : /*!
90 : * Returns a string version of the \a status enum label.
91 : */
92 221 : QString StatusService::toString(const StatusService::BatteryStatus &status)
93 : {
94 221 : switch (status) {
95 30 : case BatteryStatus::Low: return QLatin1String("Low");
96 3 : case BatteryStatus::Good: return QLatin1String("Good");
97 : }
98 : return QString();
99 : }
100 :
101 : /*!
102 : * \struct StatusService::Status
103 : * \brief Attributes included in the `Status` characterstic.
104 : *
105 : * \note Not all Pokit devices support the batteryStatus member, in which case the member will be
106 : * initilialised to the maximum value supported by the underlying type (ie `255`) to indicate "not
107 : * set".
108 : */
109 :
110 : /*!
111 : * Constructs a new Pokit service with \a parent.
112 : */
113 408 : StatusService::StatusService(QLowEnergyController * const controller, QObject * parent)
114 408 : : AbstractPokitService(new StatusServicePrivate(controller, this), parent)
115 : {
116 :
117 408 : }
118 :
119 : /*!
120 : * \cond internal
121 : * Constructs a new Pokit service with \a parent, and private implementation \a d.
122 : */
123 0 : StatusService::StatusService(
124 0 : StatusServicePrivate * const d, QObject * const parent)
125 0 : : AbstractPokitService(d, parent)
126 : {
127 :
128 0 : }
129 : /// \endcond
130 :
131 : /*!
132 : * Destroys this StatusService object.
133 : */
134 221 : StatusService::~StatusService()
135 : {
136 :
137 221 : }
138 :
139 17 : bool StatusService::readCharacteristics()
140 : {
141 11 : const bool r1 = readDeviceCharacteristics();
142 11 : const bool r2 = readStatusCharacteristic();
143 11 : const bool r3 = readNameCharacteristic();
144 17 : return (r1 && r2 && r3);
145 : }
146 :
147 : /*!
148 : * Read the `Status` service's `Device Characteristics` characteristic.
149 : *
150 : * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
151 : * underlying controller it not yet connected to the Pokit device, or the device's services have
152 : * not yet been discovered).
153 : *
154 : * Emits deviceCharacteristicsRead() if/when the characteristic has been read successfully.
155 : */
156 28 : bool StatusService::readDeviceCharacteristics()
157 : {
158 : Q_D(StatusService);
159 34 : return d->readCharacteristic(CharacteristicUuids::deviceCharacteristics);
160 : }
161 :
162 : /*!
163 : * Read the `Status` service's `Status` 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 deviceStatusRead() if/when the characteristic has been read successfully.
170 : */
171 28 : bool StatusService::readStatusCharacteristic()
172 : {
173 : Q_D(StatusService);
174 34 : return d->readCharacteristic(CharacteristicUuids::status);
175 : }
176 :
177 : /*!
178 : * Read the `Status` service's `Name` 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 deviceNameRead() if/when the characteristic has been read successfully.
185 : */
186 28 : bool StatusService::readNameCharacteristic()
187 : {
188 : Q_D(StatusService);
189 34 : return d->readCharacteristic(CharacteristicUuids::name);
190 : }
191 :
192 : /*!
193 : * Returns the most recent value of the `Status` service's `Device Characteristics` characteristic.
194 : *
195 : * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
196 : * currently available (ie the serviceDetailsDiscovered signal has not been emitted yet), then a
197 : * null result is returned, which can be checked via the returned
198 : * DeviceCharacteristics::firmwareVersion, like:
199 : *
200 : * ```
201 : * const DeviceCharacteristics characteristics = service->deviceCharacteristics();
202 : * if (!characteristics.firmwareVersion.isNull()) {
203 : * ...
204 : * }
205 : * ```
206 : */
207 34 : StatusService::DeviceCharacteristics StatusService::deviceCharacteristics() const
208 : {
209 : Q_D(const StatusService);
210 : const QLowEnergyCharacteristic characteristic =
211 34 : d->getCharacteristic(CharacteristicUuids::deviceCharacteristics);
212 34 : return (characteristic.isValid())
213 0 : ? StatusServicePrivate::parseDeviceCharacteristics(characteristic.value())
214 68 : : StatusService::DeviceCharacteristics();
215 34 : }
216 :
217 : /*!
218 : * Returns the most recent value of the `Status` service's `Status` characteristic.
219 : *
220 : * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
221 : * currently available (ie the serviceDetailsDiscovered signal has not been emitted yet), then the
222 : * returned StatusService::Status::batteryLevel member will be a quiet NaN, which can be checked
223 : * like:
224 : *
225 : * ```
226 : * const StatusService::Status status = statusService->status();
227 : * if (qIsNaN(status.batteryVoltage)) {
228 : * // Handle failure.
229 : * }
230 : * ```
231 : */
232 170 : StatusService::Status StatusService::status() const
233 : {
234 : Q_D(const StatusService);
235 : const QLowEnergyCharacteristic characteristic =
236 170 : d->getCharacteristic(CharacteristicUuids::status);
237 170 : return (characteristic.isValid()) ? StatusServicePrivate::parseStatus(characteristic.value())
238 : : StatusService::Status{ DeviceStatus::Idle, std::numeric_limits<float>::quiet_NaN(),
239 340 : BatteryStatus::Low };
240 170 : }
241 :
242 : /*!
243 : * Returns the most recent value of the `Status` services's `Device Name` characteristic.
244 : *
245 : * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
246 : * currently available (ie the serviceDetailsDiscovered signal has not been emitted yet), then a
247 : * null QString is returned.
248 : */
249 170 : QString StatusService::deviceName() const
250 : {
251 : Q_D(const StatusService);
252 : const QLowEnergyCharacteristic characteristic =
253 170 : d->getCharacteristic(CharacteristicUuids::name);
254 340 : return (characteristic.isValid()) ? QString::fromUtf8(characteristic.value()) : QString();
255 170 : }
256 :
257 : /*!
258 : * Set's the Pokit device's name to \a name.
259 : *
260 : * Returns `true` if the write request was successfully queued, `false` otherwise.
261 : *
262 : * Emits deviceNameWritten() if/when the \a name has been set.
263 : */
264 17 : bool StatusService::setDeviceName(const QString &name)
265 : {
266 : Q_D(const StatusService);
267 : const QLowEnergyCharacteristic characteristic =
268 17 : d->getCharacteristic(CharacteristicUuids::name);
269 17 : if (!characteristic.isValid()) {
270 : return false;
271 : }
272 :
273 : const QByteArray value = name.toUtf8();
274 0 : if (value.length() > 11) {
275 0 : qCWarning(d->lc).noquote() << tr("Device name \"%1\" is too long (%2 > 11 bytes): 0x3")
276 0 : .arg(name).arg(value.length()).arg(QLatin1String(value.toHex()));
277 0 : return false;
278 : }
279 :
280 0 : d->service->writeCharacteristic(characteristic, value);
281 0 : return (d->service->error() != QLowEnergyService::ServiceError::CharacteristicWriteError);
282 17 : }
283 :
284 : /*!
285 : * Flash the Pokit device's LED.
286 : *
287 : * Returns `true` if the flash request was successfully queued, `false` otherwise.
288 : *
289 : * Emits deviceLedFlashed() if/when the LED has flashed successfully.
290 : *
291 : * \note This operation is only supported by Pokit Meter devices. Pokit Pro devices will report an
292 : * Bluetooth ATT error `0x80`.
293 : *
294 : * \cond internal
295 : * \pokitApi The Android app can turn Pokit Pro LEDs on/off. Perhaps that is handled by an
296 : * undocumented use of this characteristic. Or perhaps its via some other service.
297 : * \endcond
298 : */
299 17 : bool StatusService::flashLed()
300 : {
301 : Q_D(const StatusService);
302 : const QLowEnergyCharacteristic characteristic =
303 17 : d->getCharacteristic(CharacteristicUuids::flashLed);
304 17 : if (!characteristic.isValid()) {
305 : return false;
306 : }
307 :
308 : // The Flash LED characeristic is write-only, and takes a single uint8 "LED" parameter, which
309 : // must always be 1. Presumably this is an index for which LED to flash, but the Pokit API docs
310 : // say that "any value other than 1 will be ignored", which makes sense given that all current
311 : // Pokit devices have only one LED.
312 0 : const QByteArray value(1, '\x01');
313 0 : d->service->writeCharacteristic(characteristic, value);
314 0 : return (d->service->error() != QLowEnergyService::ServiceError::CharacteristicWriteError);
315 17 : }
316 :
317 : /*!
318 : * \fn StatusService::deviceCharacteristicsRead
319 : *
320 : * This signal is emitted when the `Device Characteristics` characteristic has been read
321 : * successfully.
322 : *
323 : * \see readDeviceCharacteristics
324 : */
325 :
326 : /*!
327 : * \fn StatusService::deviceNameRead
328 : *
329 : * This signal is emitted when the `Device Name` characteristic has been read successfully.
330 : *
331 : * \see readDeviceName
332 : */
333 :
334 : /*!
335 : * \fn StatusService::deviceNameWritten
336 : *
337 : * This signal is emitted when the `Device Name` characteristic has been written successfully.
338 : *
339 : * \see setDeviceName
340 : */
341 :
342 : /*!
343 : * \fn StatusService::deviceStatusRead
344 : *
345 : * This signal is emitted when the `Status` characteristic has been read successfully.
346 : *
347 : * \see readDeviceStatus
348 : */
349 :
350 : /*!
351 : * \fn StatusService::deviceLedFlashed
352 : *
353 : * This signal is emitted when device's LED has flashed in response to a write of the `Flash LED`
354 : * characteristic.
355 : */
356 :
357 : /*!
358 : * \cond internal
359 : * \class StatusServicePrivate
360 : *
361 : * The StatusServicePrivate class provides private implementation for StatusService.
362 : */
363 :
364 : /*!
365 : * \internal
366 : * Constructs a new StatusServicePrivate object with public implementation \a q.
367 : */
368 408 : StatusServicePrivate::StatusServicePrivate(
369 408 : QLowEnergyController * controller, StatusService * const q)
370 408 : : AbstractPokitServicePrivate(QBluetoothUuid(), controller, q)
371 : {
372 :
373 408 : }
374 :
375 : /*!
376 : * Parses the `Device Characteristics` \a value into a DeviceCharacteristics struct.
377 : */
378 68 : StatusService::DeviceCharacteristics StatusServicePrivate::parseDeviceCharacteristics(
379 : const QByteArray &value)
380 : {
381 68 : StatusService::DeviceCharacteristics characteristics{
382 : QVersionNumber(), 0, 0, 0, 0, 0, 0, QBluetoothAddress()
383 68 : };
384 : Q_ASSERT(characteristics.firmwareVersion.isNull()); // How we indicate failure.
385 :
386 80 : if (!checkSize(QLatin1String("Device Characterisitcs"), value, 20, 20)) {
387 : return characteristics;
388 : }
389 :
390 40 : characteristics.firmwareVersion = QVersionNumber(
391 68 : qFromLittleEndian<quint8 >(value.mid(0,1)),
392 62 : qFromLittleEndian<quint8 >(value.mid(1,1)));
393 40 : characteristics.maximumVoltage = qFromLittleEndian<quint16>(value.mid(2,2));
394 40 : characteristics.maximumCurrent = qFromLittleEndian<quint16>(value.mid(4,2));
395 40 : characteristics.maximumResistance = qFromLittleEndian<quint16>(value.mid(6,2));
396 40 : characteristics.maximumSamplingRate = qFromLittleEndian<quint16>(value.mid(8,2));
397 40 : characteristics.samplingBufferSize = qFromLittleEndian<quint16>(value.mid(10,2));
398 40 : characteristics.capabilityMask = qFromLittleEndian<quint16>(value.mid(12,2));
399 62 : characteristics.macAddress = QBluetoothAddress(qFromBigEndian<quint64>
400 102 : (QByteArray(2, '\0') + value.mid(14,6)));
401 :
402 34 : qCDebug(lc).noquote() << tr("Firmware version: ") << characteristics.firmwareVersion;
403 34 : qCDebug(lc).noquote() << tr("Maximum voltage: ") << characteristics.maximumVoltage;
404 34 : qCDebug(lc).noquote() << tr("Maximum current: ") << characteristics.maximumCurrent;
405 34 : qCDebug(lc).noquote() << tr("Maximum resistance: ") << characteristics.maximumResistance;
406 34 : qCDebug(lc).noquote() << tr("Maximum sampling rate:") << characteristics.maximumSamplingRate;
407 34 : qCDebug(lc).noquote() << tr("Sampling buffer size: ") << characteristics.samplingBufferSize;
408 34 : qCDebug(lc).noquote() << tr("Capability mask: ") << characteristics.capabilityMask;
409 34 : qCDebug(lc).noquote() << tr("MAC address: ") << characteristics.macAddress;
410 :
411 : Q_ASSERT(!characteristics.firmwareVersion.isNull()); // How we indicate success.
412 4 : return characteristics;
413 : }
414 :
415 : /*!
416 : * Parses the `Status` \a value into Statu struct. Note, not all Pokit devices support all members
417 : * in Status. Specifically, the batteryStatus member is not usually set by Pokit Meter devices, so
418 : * will be an invlalid BatteryStatus enum value (`255`) in that case.
419 : */
420 68 : StatusService::Status StatusServicePrivate::parseStatus(const QByteArray &value)
421 : {
422 68 : StatusService::Status status{
423 : static_cast<StatusService::DeviceStatus>
424 : (std::numeric_limits<std::underlying_type<StatusService::DeviceStatus>::type>::max()),
425 : std::numeric_limits<float>::quiet_NaN(),
426 : static_cast<StatusService::BatteryStatus>
427 : (std::numeric_limits<std::underlying_type<StatusService::BatteryStatus>::type>::max()),
428 : };
429 :
430 : /*!
431 : * \pokitApi Pokit API 0.02 says the `Status` characteristic is 5 bytes. API 1.00 then added an
432 : * additional byte for `Battery Status`, for 6 bytes in total. However, Pokit Pro devices return
433 : * 8 bytes here. The purpose of those last 2 bytes are not currently known. Note also, Pokit
434 : * Meter only uses the first 5 bytes - ie `Battery Status` is not present.
435 : */
436 :
437 80 : if (!checkSize(QLatin1String("Status"), value, 5, 6)) {
438 34 : return status;
439 : }
440 :
441 34 : status.deviceStatus = static_cast<StatusService::DeviceStatus>(value.at(0));
442 40 : status.batteryVoltage = qFromLittleEndian<float>(value.mid(1,4));
443 34 : if (value.size() >= 6) { // Battery Status added to Pokit API docs v1.00.
444 17 : status.batteryStatus = static_cast<StatusService::BatteryStatus>(value.at(5));
445 : }
446 34 : qCDebug(lc).noquote() << tr("Device status: %1 (%2)")
447 0 : .arg((quint8)status.deviceStatus).arg(StatusService::toString(status.deviceStatus));
448 34 : qCDebug(lc).noquote() << tr("Battery voltage: %1 volts").arg(status.batteryVoltage);
449 34 : qCDebug(lc).noquote() << tr("Battery status: %1 (%2)")
450 0 : .arg((quint8)status.batteryStatus).arg(StatusService::toString(status.batteryStatus));
451 34 : return status;
452 : }
453 :
454 : /*!
455 : * Handles `QLowEnergyController::serviceDiscovered` events.
456 : *
457 : * Here we override the base implementation to detect if we're looking at a Pokit Meter, or Pokit
458 : * Pro device, as the two devices have very slightly different Status Service UUIDs.
459 : */
460 68 : void StatusServicePrivate::serviceDiscovered(const QBluetoothUuid &newService)
461 : {
462 68 : if (newService == StatusService::ServiceUuids::pokitMeter) {
463 17 : qCDebug(lc).noquote() << tr("Found Status Service for a Pokit Meter device.");
464 17 : serviceUuid = StatusService::ServiceUuids::pokitMeter;
465 51 : } else if (newService == StatusService::ServiceUuids::pokitPro) {
466 17 : qCDebug(lc).noquote() << tr("Found Status Service for a Pokit Pro device.");
467 17 : serviceUuid = StatusService::ServiceUuids::pokitPro;
468 : }
469 68 : AbstractPokitServicePrivate::serviceDiscovered(newService);
470 68 : }
471 :
472 : /*!
473 : * Implements AbstractPokitServicePrivate::characteristicRead to parse \a value, then emit a
474 : * specialised signal, for each supported \a characteristic.
475 : */
476 17 : void StatusServicePrivate::characteristicRead(const QLowEnergyCharacteristic &characteristic,
477 : const QByteArray &value)
478 : {
479 17 : AbstractPokitServicePrivate::characteristicRead(characteristic, value);
480 :
481 : Q_Q(StatusService);
482 17 : if (characteristic.uuid() == StatusService::CharacteristicUuids::deviceCharacteristics) {
483 0 : emit q->deviceCharacteristicsRead(parseDeviceCharacteristics(value));
484 0 : return;
485 : }
486 :
487 17 : if (characteristic.uuid() == StatusService::CharacteristicUuids::status) {
488 0 : emit q->deviceStatusRead(parseStatus(value));
489 0 : return;
490 : }
491 :
492 17 : if (characteristic.uuid() == StatusService::CharacteristicUuids::name) {
493 0 : const QString deviceName = QString::fromUtf8(value);
494 0 : qCDebug(lc).noquote() << tr("Device name: \"%1\"").arg(deviceName);
495 0 : emit q->deviceNameRead(deviceName);
496 : return;
497 0 : }
498 :
499 17 : if (characteristic.uuid() == StatusService::CharacteristicUuids::flashLed) {
500 0 : qCWarning(lc).noquote() << tr("Flash LED characteristic is write-only, but somehow read")
501 0 : << serviceUuid << characteristic.name() << characteristic.uuid();
502 0 : return;
503 : }
504 :
505 51 : qCWarning(lc).noquote() << tr("Unknown characteristic read for Status service")
506 17 : << serviceUuid << characteristic.name() << characteristic.uuid();
507 : }
508 :
509 : /*!
510 : * Implements AbstractPokitServicePrivate::characteristicWritten to parse \a newValue, then emit a
511 : * specialised signal, for each supported \a characteristic.
512 : */
513 17 : void StatusServicePrivate::characteristicWritten(const QLowEnergyCharacteristic &characteristic,
514 : const QByteArray &newValue)
515 : {
516 17 : AbstractPokitServicePrivate::characteristicWritten(characteristic, newValue);
517 :
518 : Q_Q(StatusService);
519 17 : if (characteristic.uuid() == StatusService::CharacteristicUuids::deviceCharacteristics) {
520 0 : qCWarning(lc).noquote() << tr("Device Characteristics is read-only, but somehow written")
521 0 : << serviceUuid << characteristic.name() << characteristic.uuid();
522 0 : return;
523 : }
524 :
525 17 : if (characteristic.uuid() == StatusService::CharacteristicUuids::status) {
526 0 : qCWarning(lc).noquote() << tr("Status characteristic is read-only, but somehow written")
527 0 : << serviceUuid << characteristic.name() << characteristic.uuid();
528 0 : return;
529 : }
530 :
531 17 : if (characteristic.uuid() == StatusService::CharacteristicUuids::name) {
532 0 : emit q->deviceNameWritten();
533 0 : return;
534 : }
535 :
536 17 : if (characteristic.uuid() == StatusService::CharacteristicUuids::flashLed) {
537 0 : emit q->deviceLedFlashed();
538 0 : return;
539 : }
540 :
541 51 : qCWarning(lc).noquote() << tr("Unknown characteristic written for Status service")
542 17 : << serviceUuid << characteristic.name() << characteristic.uuid();
543 : }
544 :
545 : /// \endcond
|