Dokit
Internal development documentation
Loading...
Searching...
No Matches
multimeterservice.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 MultimeterService and MultimeterServicePrivate classes.
7 */
8
10#include "multimeterservice_p.h"
11#include "pokitproducts_p.h"
12
13#include <QDataStream>
14#include <QIODevice>
15#include <QtEndian>
16
17/*!
18 * \class MultimeterService
19 *
20 * The MultimeterService class accesses the `Multimeter` service of Pokit devices.
21 */
22
23/*!
24 * \cond internal
25 * \enum MultimeterService::Mode
26 * \pokitApi The following enumeration values are as-yet undocumented by Pokit Innovations.
27 * [\@pcolby](https://github.com/pcolby) reverse-engineered them as part of the
28 * [dokit](https://github.com/pcolby/dokit) project.
29 * * Mode::Capacitance
30 * * Mode::ExternalTemperature
31 * \endcond
32 */
33
34/// Returns \a mode as a user-friendly string.
36{
37 switch (mode) {
38 case Mode::Idle: return tr("Idle");
39 case Mode::DcVoltage: return tr("DC voltage");
40 case Mode::AcVoltage: return tr("AC voltage");
41 case Mode::DcCurrent: return tr("DC current");
42 case Mode::AcCurrent: return tr("AC current");
43 case Mode::Resistance: return tr("Resistance");
44 case Mode::Diode: return tr("Diode");
45 case Mode::Continuity: return tr("Continuity");
46 case Mode::Temperature: return tr("Temperature");
47 case Mode::Capacitance: return tr("Capacitance");
48 case Mode::ExternalTemperature: return tr("External temperature");
49 }
50 return QString();
51}
52
53/// Returns \a range as a user-friendly string, or a null QString if \a mode has no ranges.
54QString MultimeterService::toString(const PokitProduct product, const quint8 range, const Mode mode)
55{
56 switch (mode) {
57 case Mode::Idle:
58 break;
59 case Mode::DcVoltage:
60 case Mode::AcVoltage:
61 return VoltageRange::toString(product, range);
62 case Mode::DcCurrent:
63 case Mode::AcCurrent:
64 return CurrentRange::toString(product, range);
66 return ResistanceRange::toString(product, range);
67 case Mode::Diode:
70 break;
72 return CapacitanceRange::toString(product, range);
74 break;
75 }
76 return QString();
77}
78
79/// Returns \a range as a user-friendly string, or a null QString if \a mode has no ranges.
80QString MultimeterService::toString(const quint8 range, const Mode mode) const
81{
82 return toString(*pokitProduct(), range, mode);
83}
84
85/*!
86 * Returns the maximum value for \a range, or the string "Auto".
87 *
88 * If \a range is not a known valid enumeration value for \a product's \a mode, then a null QVariant is returned.
89 */
90QVariant MultimeterService::maxValue(const PokitProduct product, const quint8 range, const Mode mode)
91{
92 switch (mode) {
93 case Mode::Idle:
94 break;
95 case Mode::DcVoltage:
96 case Mode::AcVoltage:
97 return VoltageRange::maxValue(product, range);
98 case Mode::DcCurrent:
99 case Mode::AcCurrent:
100 return CurrentRange::maxValue(product, range);
101 case Mode::Resistance:
102 return ResistanceRange::maxValue(product, range);
103 case Mode::Diode:
104 case Mode::Continuity:
106 break;
108 return CapacitanceRange::maxValue(product, range);
110 break;
111 }
112 return QVariant();
113}
114
115/*!
116 * Returns the maximum value for \a range, or the string "Auto".
117 *
118 * If \a range is not a known valid enumeration value for the current \a product's \a mode,
119 * then a null QVariant is returned.
120 */
121QVariant MultimeterService::maxValue(const quint8 range, const Mode mode) const
122{
123 return maxValue(*pokitProduct(), range, mode);
124}
125
126/*!
127 * Constructs a new Pokit service with \a parent.
128 */
130 : AbstractPokitService(new MultimeterServicePrivate(controller, this), parent)
131{
132
133}
134
135/*!
136 * \cond internal
137 * Constructs a new Pokit service with \a parent, and private implementation \a d.
138 */
140 MultimeterServicePrivate * const d, QObject * const parent)
141 : AbstractPokitService(d, parent)
142{
143
144}
145/// \endcond
146
151
152/*!
153 * Read the `Multimeter` service's `Reading` characteristic.
154 *
155 * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
156 * underlying controller it not yet connected to the Pokit device, or the device's services have
157 * not yet been discovered).
158 *
159 * Emits readingRead() if/when the characteristic has been read successfully.
160 */
162{
164 return d->readCharacteristic(CharacteristicUuids::reading);
165}
166
167/*!
168 * Configures the Pokit device's multimeter mode.
169 *
170 * Returns `true` if the write request was successfully queued, `false` otherwise.
171 *
172 * Emits settingsWritten() if/when the \a settings have been writtem successfully.
173 */
175{
176 Q_D(const MultimeterService);
177 const QLowEnergyCharacteristic characteristic =
178 d->getCharacteristic(CharacteristicUuids::settings);
179 if (!characteristic.isValid()) {
180 return false;
181 }
182
184 if (value.isNull()) {
185 return false;
186 }
187
188 d->service->writeCharacteristic(characteristic, value);
189 return (d->service->error() != QLowEnergyService::ServiceError::CharacteristicWriteError);
190}
191
192/*!
193 * Returns the most recent value of the `Multimeter` service's `Reading` 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 the
197 * returned MultimeterService::Reading::value member will be a quiet NaN, which can be checked like:
198 *
199 * ```
200 * const MultimeterService::Reading reading = multimeterService->reading();
201 * if (qIsNaN(reading.value)) {
202 * // Handle failure.
203 * }
204 * ```
205 */
207{
208 Q_D(const MultimeterService);
209 const QLowEnergyCharacteristic characteristic =
210 d->getCharacteristic(CharacteristicUuids::reading);
211 return (characteristic.isValid()) ? MultimeterServicePrivate::parseReading(characteristic.value())
212 : Reading{ MeterStatus::Error, std::numeric_limits<float>::quiet_NaN(), Mode::Idle, 0 };
213}
214
215/*!
216 * Enables client-side notifications of meter readings.
217 *
218 * This is an alternative to manually requesting individual reads via readReadingCharacteristic().
219 *
220 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
221 *
222 * Successfully read values (if any) will be emitted via the readingRead() signal.
223 */
225{
227 return d->enableCharacteristicNotificatons(CharacteristicUuids::reading);
228}
229
230/*!
231 * Disables client-side notifications of meter readings.
232 *
233 * Instantaneous reads can still be fetched by readReadingCharacteristic().
234 *
235 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
236 */
238{
240 return d->disableCharacteristicNotificatons(CharacteristicUuids::reading);
241}
242
243/*!
244 * \fn MultimeterService::readingRead
245 *
246 * This signal is emitted when the `Reading` characteristic has been read successfully.
247 *
248 * \see readReadingCharacteristic
249 */
250
251/*!
252 * \fn MultimeterService::settingsWritten
253 *
254 * This signal is emitted when the `Settings` characteristic has been written successfully.
255 *
256 * \see setSettings
257 */
258
259/*!
260 * \cond internal
261 * \class MultimeterServicePrivate
262 *
263 * The MultimeterServicePrivate class provides private implementation for MultimeterService.
264 */
265
266/*!
267 * \internal
268 * Constructs a new MultimeterServicePrivate object with public implementation \a q.
269 */
276
277/*!
278 * Returns \a settings in the format Pokit devices expect.
279 */
281{
282 static_assert(sizeof(settings.mode) == 1, "Expected to be 1 byte.");
283 static_assert(sizeof(settings.range) == 1, "Expected to be 1 byte.");
284 static_assert(sizeof(settings.updateInterval) == 4, "Expected to be 4 bytes.");
285
286 QByteArray value;
287 QDataStream stream(&value, QIODevice::WriteOnly);
289 stream.setFloatingPointPrecision(QDataStream::SinglePrecision); // 32-bit floats, not 64-bit.
290 stream << (quint8)settings.mode << settings.range << settings.updateInterval;
291
292 Q_ASSERT(value.size() == 6);
293 return value;
294}
295
296/*!
297 * Parses the `Reading` \a value into a MultimeterService::Reading struct.
298 */
300{
303 std::numeric_limits<float>::quiet_NaN(),
305 };
306
307 if (!checkSize(QLatin1String("Reading"), value, 7, 7)) {
308 return reading;
309 }
310
311 reading.status = MultimeterService::MeterStatus(value.at(0));
312 reading.value = qFromLittleEndian<float>(value.mid(1,4).constData());
313 reading.mode = static_cast<MultimeterService::Mode>(value.at(5));
314 reading.range = static_cast<quint8>(value.at(6));
315 return reading;
316}
317
318/*!
319 * Implements AbstractPokitServicePrivate::characteristicRead to parse \a value, then emit a
320 * specialised signal, for each supported \a characteristic.
321 */
323 const QByteArray &value)
324{
326
329 Q_EMIT q->readingRead(parseReading(value));
330 return;
331 }
332
334 qCWarning(lc).noquote() << tr("Settings characteristic is write-only, but somehow read")
335 << serviceUuid << characteristic.name() << characteristic.uuid();
336 return;
337 }
338
339 qCWarning(lc).noquote() << tr("Unknown characteristic read for Multimeter service")
340 << serviceUuid << characteristic.name() << characteristic.uuid();
341}
342
343/*!
344 * Implements AbstractPokitServicePrivate::characteristicWritten to parse \a newValue, then emit a
345 * specialised signal, for each supported \a characteristic.
346 */
348 const QByteArray &newValue)
349{
351
354 Q_EMIT q->settingsWritten();
355 return;
356 }
357
359 qCWarning(lc).noquote() << tr("Reading characteristic is read/notify, but somehow written")
360 << serviceUuid << characteristic.name() << characteristic.uuid();
361 return;
362 }
363
364 qCWarning(lc).noquote() << tr("Unknown characteristic written for Multimeter service")
365 << serviceUuid << characteristic.name() << characteristic.uuid();
366}
367
368/*!
369 * Implements AbstractPokitServicePrivate::characteristicChanged to parse \a newValue, then emit a
370 * specialised signal, for each supported \a characteristic.
371 */
373 const QByteArray &newValue)
374{
376
379 qCWarning(lc).noquote() << tr("Settings characteristic is write-only, but somehow updated")
380 << serviceUuid << characteristic.name() << characteristic.uuid();
381 return;
382 }
383
385 Q_EMIT q->readingRead(parseReading(newValue));
386 return;
387 }
388
389 qCWarning(lc).noquote() << tr("Unknown characteristic notified for Multimeter service")
390 << serviceUuid << characteristic.name() << characteristic.uuid();
391}
392
393/// \endcond
The AbstractPokitServicePrivate class provides private implementation for AbstractPokitService.
QBluetoothUuid serviceUuid
UUIDs for service.
virtual void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
Handles QLowEnergyService::characteristicChanged 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.
std::optional< PokitProduct > pokitProduct() const
Returns the Pokit product this service is attached to.
The MultimeterServicePrivate class provides private implementation for MultimeterService.
void characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) override
Implements AbstractPokitServicePrivate::characteristicRead to parse value, then emit a specialised si...
MultimeterServicePrivate(QLowEnergyController *controller, MultimeterService *const q)
static QByteArray encodeSettings(const MultimeterService::Settings &settings)
Returns settings in the format Pokit devices expect.
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) override
Implements AbstractPokitServicePrivate::characteristicWritten to parse newValue, then emit a speciali...
static MultimeterService::Reading parseReading(const QByteArray &value)
Parses the Reading value into a MultimeterService::Reading struct.
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) override
Implements AbstractPokitServicePrivate::characteristicChanged to parse newValue, then emit a speciali...
The MultimeterService class accesses the Multimeter service of Pokit devices.
bool enableReadingNotifications()
Enables client-side notifications of meter readings.
MeterStatus
Values supported by the Status attribute of the Settings characteristic.
@ Error
Error (all modes).
Mode
Values supported by the Mode attribute of the Settings and Reading characteristics.
@ Diode
Measure diode.
@ DcVoltage
Measure DC voltage.
@ Capacitance
Measure capacitance.
@ AcCurrent
Measure AC current.
@ ExternalTemperature
Measure temperature via an external temperature probe.
@ Resistance
Measure resistance.
@ AcVoltage
Measure AC voltage.
@ Idle
Make device idle.
@ Temperature
Measure temperature.
@ DcCurrent
Measure DC current.
@ Continuity
Measure continuity.
bool readCharacteristics() override
Read all characteristics.
static QString toString(const Mode &mode)
Returns mode as a user-friendly string.
MultimeterService(QLowEnergyController *const pokitDevice, QObject *parent=nullptr)
Constructs a new Pokit service with parent.
bool readReadingCharacteristic()
Read the Multimeter service's Reading characteristic.
static QVariant maxValue(const PokitProduct product, const quint8 range, const Mode mode)
Returns the maximum value for range, or the string "Auto".
bool setSettings(const Settings &settings)
Configures the Pokit device's multimeter mode.
Reading reading() const
Returns the most recent value of the Multimeter service's Reading characteristic.
bool disableReadingNotifications()
Disables client-side notifications of meter readings.
Declares the MultimeterService class.
Declares the MultimeterServicePrivate class.
QString toString(const PokitProduct product, const quint8 range)
Returns product's capacitance range as a human-friendly string.
QVariant maxValue(const PokitProduct product, const quint8 range)
Returns the maximum value for product's range in (integer) nanofarads, or the string "Auto".
QVariant maxValue(const PokitProduct product, const quint8 range)
Returns the maximum value for product's range in (integer) microamps, or the string "Auto".
QString toString(const PokitProduct product, const quint8 range)
Returns product's current range as a human-friendly string.
QVariant maxValue(const PokitProduct product, const quint8 range)
Returns the maximum value for product's range in (integer) ohms, or the string "Auto".
QString toString(const PokitProduct product, const quint8 range)
Returns product's current range as a human-friendly string.
QString toString(const PokitProduct product, const quint8 range)
Returns product's current range as a human-friendly string.
QVariant maxValue(const PokitProduct product, const quint8 range)
Returns the maximum value for product's range in (integer) millivolts, or the string "Auto".
PokitProduct
Pokit products known to, and supported by, the QtPokit library.
char at(int i) const const
const char * constData() const const
bool isNull() const const
QByteArray mid(int pos, int len) const const
int size() const const
void setByteOrder(QDataStream::ByteOrder bo)
void setFloatingPointPrecision(QDataStream::FloatingPointPrecision precision)
bool isValid() const const
QString name() const const
QBluetoothUuid uuid() const const
QByteArray value() const const
Q_EMITQ_EMIT
QString tr(const char *sourceText, const char *disambiguation, int n)
static const QBluetoothUuid reading
UUID of the Multimeter service's Reading characterstic.
static const QBluetoothUuid settings
UUID of the Multimeter service's Settings characterstic.
Attributes included in the Reading characterstic.
MeterStatus status
Current multimeter status.
Attributes included in the Settings characterstic.
quint32 updateInterval
Desired update interval in milliseconds.
Mode mode
Desired operation mode.