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 : #include "metercommand.h"
5 :
6 : #include <qtpokit/pokitdevice.h>
7 :
8 : #include <QJsonDocument>
9 : #include <QJsonObject>
10 :
11 : #include <iostream>
12 :
13 : /*!
14 : * \class MeterCommand
15 : *
16 : * The MeterCommand class implements the `meter` CLI command.
17 : */
18 :
19 : /*!
20 : * Construct a new MeterCommand object with \a parent.
21 : */
22 1105 : MeterCommand::MeterCommand(QObject * const parent) : DeviceCommand(parent),
23 2210 : service(nullptr), settings{
24 : MultimeterService::Mode::DcVoltage,
25 : MultimeterService::VoltageRange::AutoRange,
26 : 1000
27 1105 : }, samplesToGo(-1), showCsvHeader(true)
28 : {
29 :
30 1105 : }
31 :
32 833 : QStringList MeterCommand::requiredOptions(const QCommandLineParser &parser) const
33 : {
34 2499 : return DeviceCommand::requiredOptions(parser) + QStringList{
35 : QLatin1String("mode"),
36 2499 : };
37 : }
38 :
39 408 : QStringList MeterCommand::supportedOptions(const QCommandLineParser &parser) const
40 : {
41 2040 : return DeviceCommand::supportedOptions(parser) + QStringList{
42 : QLatin1String("interval"),
43 : QLatin1String("range"),
44 : QLatin1String("samples"),
45 2040 : };
46 : }
47 :
48 : /*!
49 : * \copybrief DeviceCommand::processOptions
50 : *
51 : * This implementation extends DeviceCommand::processOptions to process additional CLI options
52 : * supported (or required) by this command.
53 : */
54 391 : QStringList MeterCommand::processOptions(const QCommandLineParser &parser)
55 : {
56 391 : QStringList errors = DeviceCommand::processOptions(parser);
57 391 : if (!errors.isEmpty()) {
58 : return errors;
59 : }
60 :
61 : // Parse the (required) mode option.
62 748 : const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
63 374 : if (mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
64 17 : settings.mode = MultimeterService::Mode::AcVoltage;
65 357 : } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
66 187 : settings.mode = MultimeterService::Mode::DcVoltage;
67 170 : } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
68 17 : settings.mode = MultimeterService::Mode::AcCurrent;
69 153 : } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
70 34 : settings.mode = MultimeterService::Mode::DcCurrent;
71 119 : } else if (mode.startsWith(QLatin1String("res"))) {
72 34 : settings.mode = MultimeterService::Mode::Resistance;
73 85 : } else if (mode.startsWith(QLatin1String("dio"))) {
74 34 : settings.mode = MultimeterService::Mode::Diode;
75 51 : } else if (mode.startsWith(QLatin1String("cont"))) {
76 17 : settings.mode = MultimeterService::Mode::Continuity;
77 34 : } else if (mode.startsWith(QLatin1String("temp"))) {
78 17 : settings.mode = MultimeterService::Mode::Temperature;
79 : } else {
80 34 : errors.append(tr("Unknown meter mode: %1").arg(parser.value(QLatin1String("mode"))));
81 17 : return errors;
82 : }
83 :
84 : // Parse the interval option.
85 420 : if (parser.isSet(QLatin1String("interval"))) {
86 170 : const QString value = parser.value(QLatin1String("interval"));
87 85 : const quint32 interval = parseMilliValue(value, QLatin1String("s"), 500);
88 85 : if (interval == 0) {
89 40 : errors.append(tr("Invalid interval value: %1").arg(value));
90 : } else {
91 51 : settings.updateInterval = interval;
92 : }
93 70 : }
94 :
95 : // Parse the range option.
96 420 : if (parser.isSet(QLatin1String("range"))) {
97 306 : const QString value = parser.value(QLatin1String("range"));
98 153 : const bool isAuto = (value.trimmed().compare(QLatin1String("auto"), Qt::CaseInsensitive) == 0);
99 27 : QString unit; quint32 sensibleMinimum = 0;
100 153 : switch (settings.mode) {
101 68 : case MultimeterService::Mode::DcVoltage:
102 : case MultimeterService::Mode::AcVoltage:
103 68 : if (isAuto) {
104 17 : settings.range.voltageRange = MultimeterService::VoltageRange::AutoRange;
105 : }
106 68 : unit = QLatin1String("V");
107 : sensibleMinimum = 50; // mV.
108 68 : break;
109 51 : case MultimeterService::Mode::DcCurrent:
110 : case MultimeterService::Mode::AcCurrent:
111 51 : if (isAuto) {
112 17 : settings.range.currentRange = MultimeterService::CurrentRange::AutoRange;
113 : }
114 51 : unit = QLatin1String("A");
115 : sensibleMinimum = 5; // mA.
116 51 : break;
117 17 : case MultimeterService::Mode::Resistance:
118 17 : if (isAuto) {
119 17 : settings.range.resistanceRange = MultimeterService::ResistanceRange::AutoRange;
120 : }
121 17 : unit = QLatin1String("ohms");
122 : sensibleMinimum = 0; // Unused.
123 17 : break;
124 17 : default:
125 37 : qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
126 : }
127 153 : if ((!unit.isEmpty()) && (!isAuto)) { // isEmpty indicates a mode that has no range option.
128 : const quint32 rangeMax = (sensibleMinimum == 0)
129 85 : ? parseWholeValue(value, unit) : parseMilliValue(value, unit, sensibleMinimum);
130 85 : if (rangeMax == 0) {
131 20 : errors.append(tr("Invalid range value: %1").arg(value));
132 : } else {
133 68 : settings.range = lowestRange(settings.mode, rangeMax);
134 : }
135 : }
136 126 : }
137 :
138 : // Parse the samples option.
139 420 : if (parser.isSet(QLatin1String("samples"))) {
140 102 : const QString value = parser.value(QLatin1String("samples"));
141 51 : const quint32 samples = parseWholeValue(value, QLatin1String("S"));
142 51 : if (samples == 0) {
143 40 : errors.append(tr("Invalid samples value: %1").arg(value));
144 : } else {
145 17 : samplesToGo = samples;
146 : }
147 42 : }
148 : return errors;
149 308 : }
150 :
151 : /*!
152 : * \copybrief DeviceCommand::getService
153 : *
154 : * This override returns a pointer to a MultimeterService object.
155 : */
156 0 : AbstractPokitService * MeterCommand::getService()
157 : {
158 : Q_ASSERT(device);
159 0 : if (!service) {
160 0 : service = device->multimeter();
161 : Q_ASSERT(service);
162 0 : connect(service, &MultimeterService::settingsWritten,
163 : this, &MeterCommand::settingsWritten);
164 : }
165 0 : return service;
166 : }
167 :
168 : /*!
169 : * \copybrief DeviceCommand::serviceDetailsDiscovered
170 : *
171 : * This override fetches the current device's status, and outputs it in the selected format.
172 : */
173 0 : void MeterCommand::serviceDetailsDiscovered()
174 : {
175 0 : DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
176 0 : const QString range = MultimeterService::toString(settings.range, settings.mode);
177 0 : qCInfo(lc).noquote() << tr("Measuring %1, with range %2, every %L3ms.").arg(
178 0 : MultimeterService::toString(settings.mode),
179 0 : (range.isNull()) ? QString::fromLatin1("N/A") : range).arg(settings.updateInterval);
180 0 : service->setSettings(settings);
181 0 : }
182 :
183 : /*!
184 : * Returns the lowest \a mode range that can measure at least up to \a desired max, or AutoRange
185 : * if no such range is available.
186 : */
187 1836 : MultimeterService::Range MeterCommand::lowestRange(
188 : const MultimeterService::Mode mode, const quint32 desiredMax)
189 : {
190 1836 : MultimeterService::Range range;
191 1836 : switch (mode) {
192 714 : case MultimeterService::Mode::DcVoltage:
193 : case MultimeterService::Mode::AcVoltage:
194 714 : range.voltageRange = lowestVoltageRange(desiredMax);
195 714 : break;
196 612 : case MultimeterService::Mode::DcCurrent:
197 : case MultimeterService::Mode::AcCurrent:
198 612 : range.currentRange = lowestCurrentRange(desiredMax);
199 612 : break;
200 442 : case MultimeterService::Mode::Resistance:
201 442 : range.resistanceRange = lowestResistanceRange(desiredMax);
202 442 : break;
203 68 : default:
204 148 : qCWarning(lc).noquote() << tr("Mode does not support range.");
205 68 : range.voltageRange = MultimeterService::VoltageRange::AutoRange;
206 : }
207 1836 : return range;
208 : }
209 :
210 : #define POKIT_APP_IF_LESS_THAN_RETURN(value, label) \
211 : if (value <= MultimeterService::maxValue(MultimeterService::label).toUInt()) { \
212 : return MultimeterService::label; \
213 : }
214 :
215 : /*!
216 : * Returns the lowest current range that can measure at least up to \a desired max, or AutoRange
217 : * if no such range is available.
218 : */
219 901 : MultimeterService::CurrentRange MeterCommand::lowestCurrentRange(const quint32 desiredMax)
220 : {
221 901 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_0_to_10mA)
222 748 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_10mA_to_30mA)
223 595 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_30mA_to_150mA)
224 425 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_150mA_to_300mA)
225 272 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, CurrentRange::_300mA_to_3A)
226 : return MultimeterService::CurrentRange::AutoRange;
227 : }
228 :
229 : /*!
230 : * Returns the lowest resistance range that can measure at least up to \a desired max, or AutoRange
231 : * if no such range is available.
232 : */
233 884 : MultimeterService::ResistanceRange MeterCommand::lowestResistanceRange(const quint32 desiredMax)
234 : {
235 884 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_0_to_160)
236 782 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_160_to_330)
237 680 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_330_to_890)
238 578 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_890_to_1K5)
239 476 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_1K5_to_10K)
240 374 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_10K_to_100K)
241 272 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_100K_to_470K)
242 170 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, ResistanceRange::_470K_to_1M)
243 : return MultimeterService::ResistanceRange::AutoRange;
244 : }
245 :
246 : /*!
247 : * Returns the lowest voltage range that can measure at least up to \a desired max, or AutoRange
248 : * if no such range is available.
249 : */
250 1054 : MultimeterService::VoltageRange MeterCommand::lowestVoltageRange(const quint32 desiredMax)
251 : {
252 1054 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_0_to_300mV)
253 901 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_300mV_to_2V)
254 731 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_2V_to_6V)
255 561 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_6V_to_12V)
256 408 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_12V_to_30V)
257 255 : POKIT_APP_IF_LESS_THAN_RETURN(desiredMax, VoltageRange::_30V_to_60V)
258 : return MultimeterService::VoltageRange::AutoRange;
259 : }
260 :
261 : #undef POKIT_APP_IF_LESS_THAN_RETURN
262 :
263 : /*!
264 : * Invoked when the multimeter settings have been written, to begin reading the meter values.
265 : */
266 0 : void MeterCommand::settingsWritten()
267 : {
268 0 : qCDebug(lc).noquote() << tr("Settings written; starting meter readings...");
269 0 : connect(service, &MultimeterService::readingRead,
270 : this, &MeterCommand::outputReading);
271 0 : service->enableReadingNotifications();
272 0 : }
273 :
274 : /*!
275 : * Outputs meter \a reading in the selected ouput format.
276 : */
277 1173 : void MeterCommand::outputReading(const MultimeterService::Reading &reading)
278 : {
279 207 : QString status;
280 1173 : if (reading.status == MultimeterService::MeterStatus::Error) {
281 102 : status = QLatin1String("Error");
282 1071 : } else switch (reading.mode) {
283 : case MultimeterService::Mode::Idle:
284 : break;
285 510 : case MultimeterService::Mode::DcVoltage:
286 : case MultimeterService::Mode::AcVoltage:
287 : case MultimeterService::Mode::DcCurrent:
288 : case MultimeterService::Mode::AcCurrent:
289 : case MultimeterService::Mode::Resistance:
290 : status = (reading.status == MultimeterService::MeterStatus::AutoRangeOn)
291 930 : ? tr("Auto Range On") : tr("Auto Range Off");
292 510 : break;
293 204 : case MultimeterService::Mode::Continuity:
294 : status = (reading.status == MultimeterService::MeterStatus::Continuity)
295 372 : ? tr("Continuity") : tr("No continuity");
296 204 : break;
297 204 : case MultimeterService::Mode::Temperature:
298 : case MultimeterService::Mode::Diode:
299 204 : status = tr("Ok");
300 204 : break;
301 : }
302 :
303 1173 : QString units;
304 1173 : switch (reading.mode) {
305 : case MultimeterService::Mode::Idle: break;
306 102 : case MultimeterService::Mode::DcVoltage: units = QLatin1String("Vdc"); break;
307 102 : case MultimeterService::Mode::AcVoltage: units = QLatin1String("Vac"); break;
308 102 : case MultimeterService::Mode::DcCurrent: units = QLatin1String("Adc"); break;
309 102 : case MultimeterService::Mode::AcCurrent: units = QLatin1String("Aac"); break;
310 120 : case MultimeterService::Mode::Resistance: units = QString::fromUtf8("Ω"); break;
311 : case MultimeterService::Mode::Diode: break;
312 : case MultimeterService::Mode::Continuity: break;
313 120 : case MultimeterService::Mode::Temperature: units = QString::fromUtf8("°C"); break;
314 : }
315 :
316 1173 : QString range;
317 1173 : QVariant rangeMin, rangeMax;
318 1173 : switch (reading.mode) {
319 : case MultimeterService::Mode::Idle: break;
320 204 : case MultimeterService::Mode::DcVoltage:
321 : case MultimeterService::Mode::AcVoltage:
322 204 : range = MultimeterService::toString(reading.range.voltageRange);
323 204 : rangeMin = MultimeterService::minValue(reading.range.voltageRange);
324 204 : rangeMax = MultimeterService::maxValue(reading.range.voltageRange);
325 204 : break;
326 204 : case MultimeterService::Mode::DcCurrent:
327 : case MultimeterService::Mode::AcCurrent:
328 204 : range = MultimeterService::toString(reading.range.currentRange);
329 204 : rangeMin = MultimeterService::minValue(reading.range.currentRange);
330 204 : rangeMax = MultimeterService::maxValue(reading.range.currentRange);
331 204 : break;
332 102 : case MultimeterService::Mode::Resistance:
333 102 : range = MultimeterService::toString(reading.range.resistanceRange);
334 102 : rangeMin = MultimeterService::minValue(reading.range.resistanceRange);
335 102 : rangeMax = MultimeterService::maxValue(reading.range.resistanceRange);
336 102 : break;
337 : case MultimeterService::Mode::Diode: break;
338 : case MultimeterService::Mode::Continuity: break;
339 : case MultimeterService::Mode::Temperature: break;
340 : }
341 :
342 1173 : switch (format) {
343 : case OutputFormat::Csv:
344 612 : for (; showCsvHeader; showCsvHeader = false) {
345 260 : std::cout << qUtf8Printable(tr("mode,value,units,status,range_min_milli,range_max_milli\n"));
346 : }
347 851 : std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4,%5,%6\n")
348 : .arg(escapeCsvField(MultimeterService::toString(reading.mode)))
349 : .arg(reading.value, 0, 'f').arg(units, status, rangeMin.toString(), rangeMax.toString())
350 : );
351 391 : break;
352 : case OutputFormat::Json: {
353 : QJsonObject jsonObject{
354 69 : { QLatin1String("status"), status },
355 391 : { QLatin1String("value"), qIsInf(reading.value) ?
356 437 : QJsonValue(tr("Infinity")) : QJsonValue(reading.value) },
357 782 : { QLatin1String("mode"), MultimeterService::toString(reading.mode) },
358 3312 : };
359 391 : if ((!rangeMin.isNull()) || (!rangeMax.isNull())) {
360 880 : jsonObject.insert(QLatin1String("range"), QJsonObject{
361 30 : { QLatin1String("min"),
362 : #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
363 : (rangeMin.typeId() == QMetaType::Int)
364 : #else
365 140 : (rangeMin.type() == QVariant::Int)
366 : #endif
367 340 : ? QJsonValue(rangeMin.toInt()/1000.0) : rangeMin.toJsonValue() },
368 30 : { QLatin1String("max"),
369 : #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
370 90 : (rangeMax.typeId() == QMetaType::Int) ?
371 : #else
372 420 : (rangeMax.type() == QVariant::Int) ?
373 : #endif
374 170 : QJsonValue(rangeMax.toInt()/1000.0) : rangeMax.toJsonValue() },
375 340 : });
376 : }
377 782 : std::cout << QJsonDocument(jsonObject).toJson().toStdString();
378 391 : } break;
379 391 : case OutputFormat::Text:
380 529 : std::cout << qUtf8Printable(tr("Mode: %1 (0x%2)\n").arg(MultimeterService::toString(reading.mode))
381 : .arg((quint8)reading.mode,2,16,QLatin1Char('0')));
382 851 : std::cout << qUtf8Printable(tr("Value: %1 %2\n").arg(reading.value,0,'f').arg(units));
383 529 : std::cout << qUtf8Printable(tr("Status: %1 (0x%2)\n").arg(status)
384 : .arg((quint8)reading.status,2,16,QLatin1Char('0')));
385 529 : std::cout << qUtf8Printable(tr("Range: %1 (0x%2)\n").arg(range)
386 : .arg((quint8)reading.range.voltageRange,2,16,QLatin1Char('0')));
387 391 : break;
388 : }
389 :
390 1173 : if ((samplesToGo > 0) && (--samplesToGo == 0)) {
391 0 : if (device) disconnect(); // Will exit the application once disconnected.
392 : }
393 1173 : }
|