Line data Source code
1 : // SPDX-FileCopyrightText: 2022-2024 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 3780 : MeterCommand::MeterCommand(QObject * const parent) : DeviceCommand(parent)
23 1904 : {
24 :
25 4564 : }
26 :
27 2014 : QStringList MeterCommand::requiredOptions(const QCommandLineParser &parser) const
28 2332 : {
29 9858 : return DeviceCommand::requiredOptions(parser) + QStringList{
30 2332 : QLatin1String("mode"),
31 7367 : };
32 3498 : }
33 :
34 988 : QStringList MeterCommand::supportedOptions(const QCommandLineParser &parser) const
35 1144 : {
36 6604 : return DeviceCommand::supportedOptions(parser) + QStringList{
37 1144 : QLatin1String("interval"),
38 1144 : QLatin1String("range"),
39 1144 : QLatin1String("samples"),
40 5590 : };
41 1716 : }
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 950 : QStringList MeterCommand::processOptions(const QCommandLineParser &parser)
50 1100 : {
51 2050 : QStringList errors = DeviceCommand::processOptions(parser);
52 2050 : if (!errors.isEmpty()) {
53 44 : return errors;
54 44 : }
55 :
56 : // Parse the (required) mode option.
57 2880 : if (const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
58 2064 : mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
59 82 : settings.mode = MultimeterService::Mode::AcVoltage;
60 82 : minRangeFunc = minVoltageRange;
61 1978 : } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
62 902 : settings.mode = MultimeterService::Mode::DcVoltage;
63 902 : minRangeFunc = minVoltageRange;
64 1032 : } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
65 82 : settings.mode = MultimeterService::Mode::AcCurrent;
66 82 : minRangeFunc = minCurrentRange;
67 946 : } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
68 164 : settings.mode = MultimeterService::Mode::DcCurrent;
69 164 : minRangeFunc = minCurrentRange;
70 738 : } else if (mode.startsWith(QLatin1String("res"))) {
71 164 : settings.mode = MultimeterService::Mode::Resistance;
72 164 : minRangeFunc = minResistanceRange;
73 574 : } else if (mode.startsWith(QLatin1String("dio"))) {
74 164 : settings.mode = MultimeterService::Mode::Diode;
75 164 : minRangeFunc = nullptr;
76 410 : } else if (mode.startsWith(QLatin1String("cont"))) {
77 82 : settings.mode = MultimeterService::Mode::Continuity;
78 82 : minRangeFunc = nullptr;
79 328 : } else if (mode.startsWith(QLatin1String("temp"))) {
80 82 : settings.mode = MultimeterService::Mode::Temperature;
81 82 : minRangeFunc = nullptr;
82 246 : } else if (mode.startsWith(QLatin1String("cap"))) {
83 164 : settings.mode = MultimeterService::Mode::Capacitance;
84 164 : minRangeFunc = minCapacitanceRange;
85 88 : } else {
86 120 : errors.append(tr("Unknown meter mode: %1").arg(parser.value(QLatin1String("mode"))));
87 44 : return errors;
88 596 : }
89 :
90 : // Parse the interval option.
91 2231 : if (parser.isSet(QLatin1String("interval"))) {
92 485 : const QString value = parser.value(QLatin1String("interval"));
93 410 : const quint32 interval = parseNumber<std::milli>(value, QLatin1String("s"), 500);
94 410 : if (interval == 0) {
95 194 : errors.append(tr("Invalid interval value: %1").arg(value));
96 132 : } else {
97 246 : settings.updateInterval = interval;
98 132 : }
99 335 : }
100 :
101 : // Parse the range option.
102 1886 : rangeOptionValue = 0; // Default to auto.
103 2231 : if (parser.isSet(QLatin1String("range"))) {
104 1067 : const QString value = parser.value(QLatin1String("range"));
105 902 : if (value.trimmed().compare(QLatin1String("auto"), Qt::CaseInsensitive) != 0) {
106 574 : switch (settings.mode) {
107 88 : case MultimeterService::Mode::DcVoltage:
108 132 : case MultimeterService::Mode::AcVoltage:
109 246 : rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("V"), 50); // mV.
110 246 : break;
111 44 : case MultimeterService::Mode::DcCurrent:
112 88 : case MultimeterService::Mode::AcCurrent:
113 164 : rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("A"), 5); // mA.
114 164 : break;
115 0 : case MultimeterService::Mode::Resistance:
116 0 : rangeOptionValue = parseNumber<std::ratio<1>>(value, QLatin1String("ohms"));
117 0 : break;
118 44 : case MultimeterService::Mode::Capacitance:
119 82 : rangeOptionValue = parseNumber<std::nano>(value, QLatin1String("F"), 500); // pF.
120 82 : break;
121 82 : default:
122 143 : qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
123 308 : }
124 574 : if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
125 97 : errors.append(tr("Invalid range value: %1").arg(value));
126 44 : }
127 308 : }
128 737 : }
129 :
130 : // Parse the samples option.
131 2231 : if (parser.isSet(QLatin1String("samples"))) {
132 291 : const QString value = parser.value(QLatin1String("samples"));
133 246 : const quint32 samples = parseNumber<std::ratio<1>>(value, QLatin1String("S"));
134 246 : if (samples == 0) {
135 194 : errors.append(tr("Invalid samples value: %1").arg(value));
136 88 : } else {
137 82 : samplesToGo = samples;
138 44 : }
139 201 : }
140 1012 : return errors;
141 1012 : }
142 :
143 : /*!
144 : * \copybrief DeviceCommand::getService
145 : *
146 : * This override returns a pointer to a MultimeterService object.
147 : */
148 0 : AbstractPokitService * MeterCommand::getService()
149 0 : {
150 0 : Q_ASSERT(device);
151 0 : if (!service) {
152 0 : service = device->multimeter();
153 0 : Q_ASSERT(service);
154 0 : connect(service, &MultimeterService::settingsWritten,
155 0 : this, &MeterCommand::settingsWritten);
156 0 : }
157 0 : return service;
158 0 : }
159 :
160 : /*!
161 : * \copybrief DeviceCommand::serviceDetailsDiscovered
162 : *
163 : * This override fetches the current device's status, and outputs it in the selected format.
164 : */
165 0 : void MeterCommand::serviceDetailsDiscovered()
166 0 : {
167 0 : DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
168 0 : settings.range = (minRangeFunc == nullptr) ? 0 : minRangeFunc(*service->pokitProduct(), rangeOptionValue);
169 0 : const QString range = service->toString(settings.range, settings.mode);
170 0 : qCInfo(lc).noquote() << tr("Measuring %1, with range %2, every %L3ms.").arg(
171 0 : MultimeterService::toString(settings.mode),
172 0 : (range.isNull()) ? QString::fromLatin1("N/A") : range).arg(settings.updateInterval);
173 0 : service->setSettings(settings);
174 0 : }
175 :
176 : /*!
177 : * \var MeterCommand::minRangeFunc
178 : *
179 : * Pointer to function for converting #rangeOptionValue to a Pokit device's range enumerator. This function pointer
180 : * is assigned during the command line parsing, but is not invoked until after the device's services are discovere,
181 : * because prior to that discovery, we don't know which product (Meter vs Pro vs Clamp, etc) we're talking to and thus
182 : * which enumerator list to be using.
183 : *
184 : * If the current mode does not support ranges (eg diode, and continuity modes), then this member will be \c nullptr.
185 : *
186 : * \see processOptions
187 : * \see serviceDetailsDiscovered
188 : */
189 :
190 : /*!
191 : * Invoked when the multimeter settings have been written, to begin reading the meter values.
192 : */
193 0 : void MeterCommand::settingsWritten()
194 0 : {
195 0 : qCDebug(lc).noquote() << tr("Settings written; starting meter readings...");
196 0 : connect(service, &MultimeterService::readingRead,
197 0 : this, &MeterCommand::outputReading);
198 0 : service->enableReadingNotifications();
199 0 : }
200 :
201 : /*!
202 : * Outputs meter \a reading in the selected ouput format.
203 : */
204 2850 : void MeterCommand::outputReading(const MultimeterService::Reading &reading)
205 1200 : {
206 2325 : QString status;
207 4050 : if (reading.status == MultimeterService::MeterStatus::Error) {
208 324 : status = QLatin1String("Error");
209 3726 : } else switch (reading.mode) {
210 144 : case MultimeterService::Mode::Idle:
211 144 : break;
212 1464 : case MultimeterService::Mode::DcVoltage:
213 192 : case MultimeterService::Mode::AcVoltage:
214 288 : case MultimeterService::Mode::DcCurrent:
215 384 : case MultimeterService::Mode::AcCurrent:
216 480 : case MultimeterService::Mode::Resistance:
217 576 : case MultimeterService::Mode::Capacitance:
218 576 : status = (reading.status == MultimeterService::MeterStatus::AutoRangeOn)
219 2772 : ? tr("Auto Range On") : tr("Auto Range Off");
220 1944 : break;
221 648 : case MultimeterService::Mode::Continuity:
222 192 : status = (reading.status == MultimeterService::MeterStatus::Continuity)
223 876 : ? tr("Continuity") : tr("No continuity");
224 648 : break;
225 552 : case MultimeterService::Mode::Temperature:
226 96 : case MultimeterService::Mode::ExternalTemperature:
227 192 : case MultimeterService::Mode::Diode:
228 468 : status = tr("Ok");
229 648 : break;
230 1104 : }
231 :
232 4050 : QString unit;
233 4050 : switch (reading.mode) {
234 240 : case MultimeterService::Mode::Idle: break;
235 324 : case MultimeterService::Mode::DcVoltage: unit = QLatin1String("Vdc"); break;
236 324 : case MultimeterService::Mode::AcVoltage: unit = QLatin1String("Vac"); break;
237 324 : case MultimeterService::Mode::DcCurrent: unit = QLatin1String("Adc"); break;
238 324 : case MultimeterService::Mode::AcCurrent: unit = QLatin1String("Aac"); break;
239 414 : case MultimeterService::Mode::Resistance: unit = QString::fromUtf8("Ω"); break;
240 96 : case MultimeterService::Mode::Diode: break;
241 192 : case MultimeterService::Mode::Continuity: break;
242 414 : case MultimeterService::Mode::Temperature: unit = QString::fromUtf8("°C"); break;
243 414 : case MultimeterService::Mode::Capacitance: unit = QString::fromUtf8("F"); break;
244 0 : case MultimeterService::Mode::ExternalTemperature: unit = QString::fromUtf8("°C"); break;
245 1200 : }
246 :
247 5775 : const QString range = service->toString(reading.range, reading.mode);
248 :
249 4050 : switch (format) {
250 400 : case OutputFormat::Csv:
251 2106 : for (; showCsvHeader; showCsvHeader = false) {
252 966 : std::cout << qUtf8Printable(tr("mode,value,unit,status,range\n"));
253 224 : }
254 3050 : std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4,%5\n")
255 400 : .arg(escapeCsvField(MultimeterService::toString(reading.mode)))
256 400 : .arg(reading.value, 0, 'f').arg(unit, status, range)
257 400 : );
258 1350 : break;
259 1350 : case OutputFormat::Json: {
260 400 : QJsonObject object{
261 775 : { QLatin1String("status"), status },
262 1350 : { QLatin1String("value"), qIsInf(reading.value) ?
263 1400 : QJsonValue(tr("Infinity")) : QJsonValue(reading.value) },
264 2300 : { QLatin1String("mode"), MultimeterService::toString(reading.mode) },
265 5025 : };
266 1350 : if (!unit.isNull()) {
267 1008 : object.insert(QLatin1String("unit"), unit);
268 224 : }
269 1350 : if (!range.isNull()) {
270 792 : object.insert(QLatin1String("range"), range);
271 176 : }
272 2300 : std::cout << QJsonDocument(object).toJson().toStdString();
273 1350 : } break;
274 1350 : case OutputFormat::Text:
275 2100 : std::cout << qUtf8Printable(tr("Mode: %1 (0x%2)\n").arg(MultimeterService::toString(reading.mode))
276 400 : .arg((quint8)reading.mode,2,16,QLatin1Char('0')));
277 2675 : std::cout << qUtf8Printable(tr("Value: %1 %2\n").arg(reading.value,0,'f').arg(unit));
278 2100 : std::cout << qUtf8Printable(tr("Status: %1 (0x%2)\n").arg(status)
279 400 : .arg((quint8)reading.status,2,16,QLatin1Char('0')));
280 2100 : std::cout << qUtf8Printable(tr("Range: %1 (0x%2)\n").arg(range)
281 400 : .arg((quint8)reading.range,2,16,QLatin1Char('0')));
282 1350 : break;
283 1200 : }
284 :
285 4050 : if ((samplesToGo > 0) && (--samplesToGo == 0)) {
286 0 : if (device) disconnect(); // Will exit the application once disconnected.
287 0 : }
288 8050 : }
|