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