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
147/*!
148 * Destroys this MultimeterService object.
149 */
154
159
160/*!
161 * Read the `Multimeter` service's `Reading` characteristic.
162 *
163 * Returns `true` is the read request is succesfully queued, `false` otherwise (ie if the
164 * underlying controller it not yet connected to the Pokit device, or the device's services have
165 * not yet been discovered).
166 *
167 * Emits readingRead() if/when the characteristic has been read successfully.
168 */
170{
172 return d->readCharacteristic(CharacteristicUuids::reading);
173}
174
175/*!
176 * Configures the Pokit device's multimeter mode.
177 *
178 * Returns `true` if the write request was successfully queued, `false` otherwise.
179 *
180 * Emits settingsWritten() if/when the \a settings have been writtem successfully.
181 */
183{
184 Q_D(const MultimeterService);
185 const QLowEnergyCharacteristic characteristic =
186 d->getCharacteristic(CharacteristicUuids::settings);
187 if (!characteristic.isValid()) {
188 return false;
189 }
190
192 if (value.isNull()) {
193 return false;
194 }
195
196 d->service->writeCharacteristic(characteristic, value);
197 return (d->service->error() != QLowEnergyService::ServiceError::CharacteristicWriteError);
198}
199
200/*!
201 * Returns the most recent value of the `Multimeter` service's `Reading` characteristic.
202 *
203 * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is
204 * currently available (ie the serviceDetailsDiscovered signal has not been emitted yet), then the
205 * returned MultimeterService::Reading::value member will be a quiet NaN, which can be checked like:
206 *
207 * ```
208 * const MultimeterService::Reading reading = multimeterService->reading();
209 * if (qIsNaN(reading.value)) {
210 * // Handle failure.
211 * }
212 * ```
213 */
215{
216 Q_D(const MultimeterService);
217 const QLowEnergyCharacteristic characteristic =
218 d->getCharacteristic(CharacteristicUuids::reading);
219 return (characteristic.isValid()) ? MultimeterServicePrivate::parseReading(characteristic.value())
220 : Reading{ MeterStatus::Error, std::numeric_limits<float>::quiet_NaN(), Mode::Idle, 0 };
221}
222
223/*!
224 * Enables client-side notifications of meter readings.
225 *
226 * This is an alternative to manually requesting individual reads via readReadingCharacteristic().
227 *
228 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
229 *
230 * Successfully read values (if any) will be emitted via the readingRead() signal.
231 */
233{
235 return d->enableCharacteristicNotificatons(CharacteristicUuids::reading);
236}
237
238/*!
239 * Disables client-side notifications of meter readings.
240 *
241 * Instantaneous reads can still be fetched by readReadingCharacteristic().
242 *
243 * Returns `true` is the request was successfully submited to the device queue, `false` otherwise.
244 */
246{
248 return d->disableCharacteristicNotificatons(CharacteristicUuids::reading);
249}
250
251/*!
252 * \fn MultimeterService::readingRead
253 *
254 * This signal is emitted when the `Reading` characteristic has been read successfully.
255 *
256 * \see readReadingCharacteristic
257 */
258
259/*!
260 * \fn MultimeterService::settingsWritten
261 *
262 * This signal is emitted when the `Settings` characteristic has been written successfully.
263 *
264 * \see setSettings
265 */
266
267/*!
268 * \cond internal
269 * \class MultimeterServicePrivate
270 *
271 * The MultimeterServicePrivate class provides private implementation for MultimeterService.
272 */
273
274/*!
275 * \internal
276 * Constructs a new MultimeterServicePrivate object with public implementation \a q.
277 */
284
285/*!
286 * Returns \a settings in the format Pokit devices expect.
287 */
289{
290 static_assert(sizeof(settings.mode) == 1, "Expected to be 1 byte.");
291 static_assert(sizeof(settings.range) == 1, "Expected to be 1 byte.");
292 static_assert(sizeof(settings.updateInterval) == 4, "Expected to be 4 bytes.");
293
294 QByteArray value;
295 QDataStream stream(&value, QIODevice::WriteOnly);
297 stream.setFloatingPointPrecision(QDataStream::SinglePrecision); // 32-bit floats, not 64-bit.
298 stream << (quint8)settings.mode << settings.range << settings.updateInterval;
299
300 Q_ASSERT(value.size() == 6);
301 return value;
302}
303
304/*!
305 * Parses the `Reading` \a value into a MultimeterService::Reading struct.
306 */
308{
311 std::numeric_limits<float>::quiet_NaN(),
313 };
314
315 if (!checkSize(QLatin1String("Reading"), value, 7, 7)) {
316 return reading;
317 }
318
319 reading.status = MultimeterService::MeterStatus(value.at(0));
320 reading.value = qFromLittleEndian<float>(value.mid(1,4).constData());
321 reading.mode = static_cast<MultimeterService::Mode>(value.at(5));
322 reading.range = static_cast<quint8>(value.at(6));
323 return reading;
324}
325
326/*!
327 * Implements AbstractPokitServicePrivate::characteristicRead to parse \a value, then emit a
328 * specialised signal, for each supported \a characteristic.
329 */
331 const QByteArray &value)
332{
334
337 Q_EMIT q->readingRead(parseReading(value));
338 return;
339 }
340
342 qCWarning(lc).noquote() << tr("Settings characteristic is write-only, but somehow read")
343 << serviceUuid << characteristic.name() << characteristic.uuid();
344 return;
345 }
346
347 qCWarning(lc).noquote() << tr("Unknown characteristic read for Multimeter service")
348 << serviceUuid << characteristic.name() << characteristic.uuid();
349}
350
351/*!
352 * Implements AbstractPokitServicePrivate::characteristicWritten to parse \a newValue, then emit a
353 * specialised signal, for each supported \a characteristic.
354 */
356 const QByteArray &newValue)
357{
359
362 Q_EMIT q->settingsWritten();
363 return;
364 }
365
367 qCWarning(lc).noquote() << tr("Reading characteristic is read/notify, but somehow written")
368 << serviceUuid << characteristic.name() << characteristic.uuid();
369 return;
370 }
371
372 qCWarning(lc).noquote() << tr("Unknown characteristic written for Multimeter service")
373 << serviceUuid << characteristic.name() << characteristic.uuid();
374}
375
376/*!
377 * Implements AbstractPokitServicePrivate::characteristicChanged to parse \a newValue, then emit a
378 * specialised signal, for each supported \a characteristic.
379 */
381 const QByteArray &newValue)
382{
384
387 qCWarning(lc).noquote() << tr("Settings characteristic is write-only, but somehow updated")
388 << serviceUuid << characteristic.name() << characteristic.uuid();
389 return;
390 }
391
393 Q_EMIT q->readingRead(parseReading(newValue));
394 return;
395 }
396
397 qCWarning(lc).noquote() << tr("Unknown characteristic notified for Multimeter service")
398 << serviceUuid << characteristic.name() << characteristic.uuid();
399}
400
401/// \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.
~MultimeterService() override
Destroys this MultimeterService object.
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.