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 1260 : MeterCommand::MeterCommand(QObject * const parent) : DeviceCommand(parent)
23 : {
24 :
25 1260 : }
26 :
27 954 : QStringList MeterCommand::requiredOptions(const QCommandLineParser &parser) const
28 : {
29 3180 : return DeviceCommand::requiredOptions(parser) + QStringList{
30 : QLatin1String("mode"),
31 2809 : };
32 : }
33 :
34 468 : QStringList MeterCommand::supportedOptions(const QCommandLineParser &parser) const
35 : {
36 2496 : return DeviceCommand::supportedOptions(parser) + QStringList{
37 : QLatin1String("interval"),
38 : QLatin1String("range"),
39 : QLatin1String("samples"),
40 2314 : };
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 450 : QStringList MeterCommand::processOptions(const QCommandLineParser &parser)
50 : {
51 450 : QStringList errors = DeviceCommand::processOptions(parser);
52 450 : if (!errors.isEmpty()) {
53 : return errors;
54 : }
55 :
56 : // Parse the (required) mode option.
57 864 : const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
58 432 : if (mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
59 18 : settings.mode = MultimeterService::Mode::AcVoltage;
60 18 : minRangeFunc = minVoltageRange;
61 414 : } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
62 198 : settings.mode = MultimeterService::Mode::DcVoltage;
63 198 : minRangeFunc = minVoltageRange;
64 216 : } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
65 18 : settings.mode = MultimeterService::Mode::AcCurrent;
66 18 : minRangeFunc = minCurrentRange;
67 198 : } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
68 36 : settings.mode = MultimeterService::Mode::DcCurrent;
69 36 : minRangeFunc = minCurrentRange;
70 162 : } else if (mode.startsWith(QLatin1String("res"))) {
71 36 : settings.mode = MultimeterService::Mode::Resistance;
72 36 : minRangeFunc = minResistanceRange;
73 126 : } else if (mode.startsWith(QLatin1String("dio"))) {
74 36 : settings.mode = MultimeterService::Mode::Diode;
75 36 : minRangeFunc = nullptr;
76 90 : } else if (mode.startsWith(QLatin1String("cont"))) {
77 18 : settings.mode = MultimeterService::Mode::Continuity;
78 18 : minRangeFunc = nullptr;
79 72 : } else if (mode.startsWith(QLatin1String("temp"))) {
80 18 : settings.mode = MultimeterService::Mode::Temperature;
81 18 : minRangeFunc = nullptr;
82 54 : } else if (mode.startsWith(QLatin1String("cap"))) {
83 36 : settings.mode = MultimeterService::Mode::Capacitance;
84 36 : minRangeFunc = minCapacitanceRange;
85 : } else {
86 36 : errors.append(tr("Unknown meter mode: %1").arg(parser.value(QLatin1String("mode"))));
87 18 : return errors;
88 : }
89 :
90 : // Parse the interval option.
91 506 : if (parser.isSet(QLatin1String("interval"))) {
92 150 : const QString value = parser.value(QLatin1String("interval"));
93 90 : const quint32 interval = parseNumber<std::milli>(value, QLatin1String("s"), 500);
94 90 : if (interval == 0) {
95 44 : errors.append(tr("Invalid interval value: %1").arg(value));
96 : } else {
97 54 : settings.updateInterval = interval;
98 : }
99 70 : }
100 :
101 : // Parse the range option.
102 414 : rangeOptionValue = 0; // Default to auto.
103 506 : if (parser.isSet(QLatin1String("range"))) {
104 330 : const QString value = parser.value(QLatin1String("range"));
105 198 : if (value.trimmed().compare(QLatin1String("auto"), Qt::CaseInsensitive) != 0) {
106 126 : switch (settings.mode) {
107 : case MultimeterService::Mode::DcVoltage:
108 : case MultimeterService::Mode::AcVoltage:
109 54 : rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("V"), 50); // mV.
110 54 : break;
111 : case MultimeterService::Mode::DcCurrent:
112 : case MultimeterService::Mode::AcCurrent:
113 36 : rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("A"), 5); // mA.
114 36 : break;
115 : case MultimeterService::Mode::Resistance:
116 0 : rangeOptionValue = parseNumber<std::ratio<1>>(value, QLatin1String("ohms"));
117 0 : break;
118 : case MultimeterService::Mode::Capacitance:
119 18 : rangeOptionValue = parseNumber<std::nano>(value, QLatin1String("F"), 500); // pF.
120 18 : break;
121 18 : default:
122 40 : qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
123 : }
124 126 : if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
125 22 : errors.append(tr("Invalid range value: %1").arg(value));
126 : }
127 : }
128 154 : }
129 :
130 : // Parse the samples option.
131 506 : if (parser.isSet(QLatin1String("samples"))) {
132 90 : const QString value = parser.value(QLatin1String("samples"));
133 54 : const quint32 samples = parseNumber<std::ratio<1>>(value, QLatin1String("S"));
134 54 : if (samples == 0) {
135 44 : errors.append(tr("Invalid samples value: %1").arg(value));
136 : } else {
137 18 : samplesToGo = samples;
138 : }
139 42 : }
140 : return errors;
141 336 : }
142 :
143 : /*!
144 : * \copybrief DeviceCommand::getService
145 : *
146 : * This override returns a pointer to a MultimeterService object.
147 : */
148 0 : AbstractPokitService * MeterCommand::getService()
149 : {
150 : Q_ASSERT(device);
151 0 : if (!service) {
152 0 : service = device->multimeter();
153 : Q_ASSERT(service);
154 0 : connect(service, &MultimeterService::settingsWritten,
155 : this, &MeterCommand::settingsWritten);
156 : }
157 0 : return service;
158 : }
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 : {
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 : {
195 0 : qCDebug(lc).noquote() << tr("Settings written; starting meter readings...");
196 0 : connect(service, &MultimeterService::readingRead,
197 : this, &MeterCommand::outputReading);
198 0 : service->enableReadingNotifications();
199 0 : }
200 :
201 : /*!
202 : * Outputs meter \a reading in the selected ouput format.
203 : */
204 1350 : void MeterCommand::outputReading(const MultimeterService::Reading &reading)
205 : {
206 300 : QString status;
207 1350 : if (reading.status == MultimeterService::MeterStatus::Error) {
208 108 : status = QLatin1String("Error");
209 1242 : } else switch (reading.mode) {
210 : case MultimeterService::Mode::Idle:
211 : break;
212 648 : case MultimeterService::Mode::DcVoltage:
213 : case MultimeterService::Mode::AcVoltage:
214 : case MultimeterService::Mode::DcCurrent:
215 : case MultimeterService::Mode::AcCurrent:
216 : case MultimeterService::Mode::Resistance:
217 : case MultimeterService::Mode::Capacitance:
218 : status = (reading.status == MultimeterService::MeterStatus::AutoRangeOn)
219 1152 : ? tr("Auto Range On") : tr("Auto Range Off");
220 648 : break;
221 216 : case MultimeterService::Mode::Continuity:
222 : status = (reading.status == MultimeterService::MeterStatus::Continuity)
223 384 : ? tr("Continuity") : tr("No continuity");
224 216 : break;
225 216 : case MultimeterService::Mode::Temperature:
226 : case MultimeterService::Mode::ExternalTemperature:
227 : case MultimeterService::Mode::Diode:
228 216 : status = tr("Ok");
229 216 : break;
230 : }
231 :
232 1350 : QString unit;
233 1350 : switch (reading.mode) {
234 : case MultimeterService::Mode::Idle: break;
235 108 : case MultimeterService::Mode::DcVoltage: unit = QLatin1String("Vdc"); break;
236 108 : case MultimeterService::Mode::AcVoltage: unit = QLatin1String("Vac"); break;
237 108 : case MultimeterService::Mode::DcCurrent: unit = QLatin1String("Adc"); break;
238 108 : case MultimeterService::Mode::AcCurrent: unit = QLatin1String("Aac"); break;
239 132 : case MultimeterService::Mode::Resistance: unit = QString::fromUtf8("Ω"); break;
240 : case MultimeterService::Mode::Diode: break;
241 : case MultimeterService::Mode::Continuity: break;
242 132 : case MultimeterService::Mode::Temperature: unit = QString::fromUtf8("°C"); break;
243 132 : case MultimeterService::Mode::Capacitance: unit = QString::fromUtf8("F"); break;
244 0 : case MultimeterService::Mode::ExternalTemperature: unit = QString::fromUtf8("°C"); break;
245 : }
246 :
247 2400 : const QString range = service->toString(reading.range, reading.mode);
248 :
249 1350 : switch (format) {
250 : case OutputFormat::Csv:
251 702 : for (; showCsvHeader; showCsvHeader = false) {
252 308 : std::cout << qUtf8Printable(tr("mode,value,unit,status,range\n"));
253 : }
254 1000 : std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4,%5\n")
255 : .arg(escapeCsvField(MultimeterService::toString(reading.mode)))
256 : .arg(reading.value, 0, 'f').arg(unit, status, range)
257 : );
258 450 : break;
259 150 : case OutputFormat::Json: {
260 : QJsonObject object{
261 100 : { QLatin1String("status"), status },
262 450 : { QLatin1String("value"), qIsInf(reading.value) ?
263 500 : QJsonValue(tr("Infinity")) : QJsonValue(reading.value) },
264 900 : { QLatin1String("mode"), MultimeterService::toString(reading.mode) },
265 3025 : };
266 450 : if (!unit.isNull()) {
267 378 : object.insert(QLatin1String("unit"), unit);
268 : }
269 450 : if (!range.isNull()) {
270 297 : object.insert(QLatin1String("range"), range);
271 : }
272 900 : std::cout << QJsonDocument(object).toJson().toStdString();
273 450 : } break;
274 450 : case OutputFormat::Text:
275 650 : std::cout << qUtf8Printable(tr("Mode: %1 (0x%2)\n").arg(MultimeterService::toString(reading.mode))
276 : .arg((quint8)reading.mode,2,16,QLatin1Char('0')));
277 1000 : std::cout << qUtf8Printable(tr("Value: %1 %2\n").arg(reading.value,0,'f').arg(unit));
278 650 : std::cout << qUtf8Printable(tr("Status: %1 (0x%2)\n").arg(status)
279 : .arg((quint8)reading.status,2,16,QLatin1Char('0')));
280 650 : std::cout << qUtf8Printable(tr("Range: %1 (0x%2)\n").arg(range)
281 : .arg((quint8)reading.range,2,16,QLatin1Char('0')));
282 450 : break;
283 : }
284 :
285 1350 : if ((samplesToGo > 0) && (--samplesToGo == 0)) {
286 0 : if (device) disconnect(); // Will exit the application once disconnected.
287 : }
288 2250 : }
|