Line data Source code
1 : // SPDX-FileCopyrightText: 2022-2025 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 3990 : MeterCommand::MeterCommand(QObject * const parent) : DeviceCommand(parent)
23 2030 : {
24 :
25 4830 : }
26 :
27 2120 : QStringList MeterCommand::requiredOptions(const QCommandLineParser &parser) const
28 2491 : {
29 10441 : return DeviceCommand::requiredOptions(parser) + QStringList{
30 2491 : QLatin1String("mode"),
31 7738 : };
32 3657 : }
33 :
34 1040 : QStringList MeterCommand::supportedOptions(const QCommandLineParser &parser) const
35 1222 : {
36 6994 : return DeviceCommand::supportedOptions(parser) + QStringList{
37 1222 : QLatin1String("interval"),
38 1222 : QLatin1String("range"),
39 1222 : QLatin1String("samples"),
40 5876 : };
41 1794 : }
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 1000 : QStringList MeterCommand::processOptions(const QCommandLineParser &parser)
50 1175 : {
51 2175 : QStringList errors = DeviceCommand::processOptions(parser);
52 2175 : if (!errors.isEmpty()) {
53 47 : return errors;
54 47 : }
55 :
56 : // Parse the (required) mode option.
57 3048 : if (const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
58 2232 : mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
59 87 : settings.mode = MultimeterService::Mode::AcVoltage;
60 87 : minRangeFunc = minVoltageRange;
61 2139 : } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
62 957 : settings.mode = MultimeterService::Mode::DcVoltage;
63 957 : minRangeFunc = minVoltageRange;
64 1116 : } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
65 87 : settings.mode = MultimeterService::Mode::AcCurrent;
66 87 : minRangeFunc = minCurrentRange;
67 1023 : } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
68 174 : settings.mode = MultimeterService::Mode::DcCurrent;
69 174 : minRangeFunc = minCurrentRange;
70 783 : } else if (mode.startsWith(QLatin1String("res"))) {
71 174 : settings.mode = MultimeterService::Mode::Resistance;
72 174 : minRangeFunc = minResistanceRange;
73 609 : } else if (mode.startsWith(QLatin1String("dio"))) {
74 174 : settings.mode = MultimeterService::Mode::Diode;
75 174 : minRangeFunc = nullptr;
76 435 : } else if (mode.startsWith(QLatin1String("cont"))) {
77 87 : settings.mode = MultimeterService::Mode::Continuity;
78 87 : minRangeFunc = nullptr;
79 348 : } else if (mode.startsWith(QLatin1String("temp"))) {
80 87 : settings.mode = MultimeterService::Mode::Temperature;
81 87 : minRangeFunc = nullptr;
82 261 : } else if (mode.startsWith(QLatin1String("cap"))) {
83 174 : settings.mode = MultimeterService::Mode::Capacitance;
84 174 : minRangeFunc = minCapacitanceRange;
85 94 : } else {
86 129 : errors.append(tr("Unknown meter mode: %1").arg(parser.value(QLatin1String("mode"))));
87 47 : return errors;
88 599 : }
89 :
90 : // Parse the interval option.
91 2392 : if (parser.isSet(QLatin1String("interval"))) {
92 520 : const QString value = parser.value(QLatin1String("interval"));
93 435 : const quint32 interval = parseNumber<std::milli>(value, QLatin1String("s"), 500);
94 435 : if (interval == 0) {
95 208 : errors.append(tr("Invalid interval value: %1").arg(value));
96 141 : } else {
97 261 : settings.updateInterval = interval;
98 141 : }
99 350 : }
100 :
101 : // Parse the range option.
102 2001 : rangeOptionValue = 0; // Default to auto.
103 2392 : if (parser.isSet(QLatin1String("range"))) {
104 1144 : const QString value = parser.value(QLatin1String("range"));
105 957 : if (value.trimmed().compare(QLatin1String("auto"), Qt::CaseInsensitive) != 0) {
106 609 : switch (settings.mode) {
107 94 : case MultimeterService::Mode::DcVoltage:
108 141 : case MultimeterService::Mode::AcVoltage:
109 261 : rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("V"), 50); // mV.
110 261 : break;
111 47 : case MultimeterService::Mode::DcCurrent:
112 94 : case MultimeterService::Mode::AcCurrent:
113 174 : rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("A"), 5); // mA.
114 174 : break;
115 0 : case MultimeterService::Mode::Resistance:
116 0 : rangeOptionValue = parseNumber<std::ratio<1>>(value, QLatin1String("ohms"));
117 0 : break;
118 47 : case MultimeterService::Mode::Capacitance:
119 87 : rangeOptionValue = parseNumber<std::nano>(value, QLatin1String("F"), 500); // pF.
120 87 : break;
121 87 : default:
122 154 : qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
123 329 : }
124 609 : if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
125 104 : errors.append(tr("Invalid range value: %1").arg(value));
126 47 : }
127 329 : }
128 770 : }
129 :
130 : // Parse the samples option.
131 2392 : if (parser.isSet(QLatin1String("samples"))) {
132 312 : const QString value = parser.value(QLatin1String("samples"));
133 261 : const quint32 samples = parseNumber<std::ratio<1>>(value, QLatin1String("S"));
134 261 : if (samples == 0) {
135 208 : errors.append(tr("Invalid samples value: %1").arg(value));
136 94 : } else {
137 87 : samplesToGo = samples;
138 47 : }
139 210 : }
140 1081 : return errors;
141 1081 : }
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 3000 : void MeterCommand::outputReading(const MultimeterService::Reading &reading)
205 1275 : {
206 2550 : QString status;
207 4275 : if (reading.status == MultimeterService::MeterStatus::Error) {
208 342 : status = QLatin1String("Error");
209 3933 : } else switch (reading.mode) {
210 153 : case MultimeterService::Mode::Idle:
211 153 : break;
212 1542 : case MultimeterService::Mode::DcVoltage:
213 204 : case MultimeterService::Mode::AcVoltage:
214 306 : case MultimeterService::Mode::DcCurrent:
215 408 : case MultimeterService::Mode::AcCurrent:
216 510 : case MultimeterService::Mode::Resistance:
217 612 : case MultimeterService::Mode::Capacitance:
218 612 : status = (reading.status == MultimeterService::MeterStatus::AutoRangeOn)
219 2880 : ? tr("Auto Range On") : tr("Auto Range Off");
220 2052 : break;
221 684 : case MultimeterService::Mode::Continuity:
222 204 : status = (reading.status == MultimeterService::MeterStatus::Continuity)
223 912 : ? tr("Continuity") : tr("No continuity");
224 684 : break;
225 582 : case MultimeterService::Mode::Temperature:
226 102 : case MultimeterService::Mode::ExternalTemperature:
227 204 : case MultimeterService::Mode::Diode:
228 480 : status = tr("Ok");
229 684 : break;
230 1173 : }
231 :
232 4275 : QString unit;
233 4275 : switch (reading.mode) {
234 255 : case MultimeterService::Mode::Idle: break;
235 342 : case MultimeterService::Mode::DcVoltage: unit = QLatin1String("Vdc"); break;
236 342 : case MultimeterService::Mode::AcVoltage: unit = QLatin1String("Vac"); break;
237 342 : case MultimeterService::Mode::DcCurrent: unit = QLatin1String("Adc"); break;
238 342 : case MultimeterService::Mode::AcCurrent: unit = QLatin1String("Aac"); break;
239 444 : case MultimeterService::Mode::Resistance: unit = QString::fromUtf8("Ω"); break;
240 102 : case MultimeterService::Mode::Diode: break;
241 204 : case MultimeterService::Mode::Continuity: break;
242 444 : case MultimeterService::Mode::Temperature: unit = QString::fromUtf8("°C"); break;
243 444 : case MultimeterService::Mode::Capacitance: unit = QString::fromUtf8("F"); break;
244 0 : case MultimeterService::Mode::ExternalTemperature: unit = QString::fromUtf8("°C"); break;
245 1275 : }
246 :
247 6000 : const QString range = service->toString(reading.range, reading.mode);
248 :
249 4275 : switch (format) {
250 425 : case OutputFormat::Csv:
251 2223 : for (; showCsvHeader; showCsvHeader = false) {
252 1036 : std::cout << qUtf8Printable(tr("mode,value,unit,status,range\n"));
253 238 : }
254 3275 : std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4,%5\n")
255 425 : .arg(escapeCsvField(MultimeterService::toString(reading.mode)))
256 425 : .arg(reading.value, 0, 'f').arg(unit, status, range)
257 425 : );
258 1425 : break;
259 1425 : case OutputFormat::Json: {
260 425 : QJsonObject object{
261 850 : { QLatin1String("status"), status },
262 1425 : { QLatin1String("value"), qIsInf(reading.value) ?
263 1475 : QJsonValue(tr("Infinity")) : QJsonValue(reading.value) },
264 2425 : { QLatin1String("mode"), MultimeterService::toString(reading.mode) },
265 5300 : };
266 1425 : if (!unit.isNull()) {
267 1078 : object.insert(QLatin1String("unit"), unit);
268 238 : }
269 1425 : if (!range.isNull()) {
270 847 : object.insert(QLatin1String("range"), range);
271 187 : }
272 2425 : std::cout << QJsonDocument(object).toJson().toStdString();
273 1425 : } break;
274 1425 : case OutputFormat::Text:
275 2325 : std::cout << qUtf8Printable(tr("Mode: %1 (0x%2)\n").arg(MultimeterService::toString(reading.mode))
276 425 : .arg((quint8)reading.mode,2,16,QLatin1Char('0')));
277 2850 : std::cout << qUtf8Printable(tr("Value: %1 %2\n").arg(reading.value,0,'f').arg(unit));
278 2275 : std::cout << qUtf8Printable(tr("Status: %1 (0x%2)\n").arg(status)
279 425 : .arg((quint8)reading.status,2,16,QLatin1Char('0')));
280 2225 : std::cout << qUtf8Printable(tr("Range: %1 (0x%2)\n").arg(range)
281 425 : .arg((quint8)reading.range,2,16,QLatin1Char('0')));
282 1425 : break;
283 1275 : }
284 :
285 4275 : if ((samplesToGo > 0) && (--samplesToGo == 0)) {
286 0 : if (device) disconnect(); // Will exit the application once disconnected.
287 0 : }
288 8425 : }
|