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 1400 : MeterCommand::MeterCommand(QObject * const parent) : DeviceCommand(parent)
23 : {
24 :
25 1400 : }
26 :
27 1060 : QStringList MeterCommand::requiredOptions(const QCommandLineParser &parser) const
28 : {
29 3498 : return DeviceCommand::requiredOptions(parser) + QStringList{
30 : QLatin1String("mode"),
31 3074 : };
32 : }
33 :
34 520 : QStringList MeterCommand::supportedOptions(const QCommandLineParser &parser) const
35 : {
36 2756 : return DeviceCommand::supportedOptions(parser) + QStringList{
37 : QLatin1String("interval"),
38 : QLatin1String("range"),
39 : QLatin1String("samples"),
40 2548 : };
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 500 : QStringList MeterCommand::processOptions(const QCommandLineParser &parser)
50 : {
51 500 : QStringList errors = DeviceCommand::processOptions(parser);
52 500 : if (!errors.isEmpty()) {
53 : return errors;
54 : }
55 :
56 : // Parse the (required) mode option.
57 960 : const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
58 480 : if (mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
59 20 : settings.mode = MultimeterService::Mode::AcVoltage;
60 20 : minRangeFunc = minVoltageRange;
61 460 : } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
62 220 : settings.mode = MultimeterService::Mode::DcVoltage;
63 220 : minRangeFunc = minVoltageRange;
64 240 : } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
65 20 : settings.mode = MultimeterService::Mode::AcCurrent;
66 20 : minRangeFunc = minCurrentRange;
67 220 : } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
68 40 : settings.mode = MultimeterService::Mode::DcCurrent;
69 40 : minRangeFunc = minCurrentRange;
70 180 : } else if (mode.startsWith(QLatin1String("res"))) {
71 40 : settings.mode = MultimeterService::Mode::Resistance;
72 40 : minRangeFunc = minResistanceRange;
73 140 : } else if (mode.startsWith(QLatin1String("dio"))) {
74 40 : settings.mode = MultimeterService::Mode::Diode;
75 40 : minRangeFunc = nullptr;
76 100 : } else if (mode.startsWith(QLatin1String("cont"))) {
77 20 : settings.mode = MultimeterService::Mode::Continuity;
78 20 : minRangeFunc = nullptr;
79 80 : } else if (mode.startsWith(QLatin1String("temp"))) {
80 20 : settings.mode = MultimeterService::Mode::Temperature;
81 20 : minRangeFunc = nullptr;
82 60 : } else if (mode.startsWith(QLatin1String("cap"))) {
83 40 : settings.mode = MultimeterService::Mode::Capacitance;
84 40 : minRangeFunc = minCapacitanceRange;
85 : } else {
86 40 : errors.append(tr("Unknown meter mode: %1").arg(parser.value(QLatin1String("mode"))));
87 20 : return errors;
88 : }
89 :
90 : // Parse the interval option.
91 598 : if (parser.isSet(QLatin1String("interval"))) {
92 170 : const QString value = parser.value(QLatin1String("interval"));
93 100 : const quint32 interval = parseNumber<std::milli>(value, QLatin1String("s"), 500);
94 100 : if (interval == 0) {
95 52 : errors.append(tr("Invalid interval value: %1").arg(value));
96 : } else {
97 60 : settings.updateInterval = interval;
98 : }
99 70 : }
100 :
101 : // Parse the range option.
102 460 : rangeOptionValue = 0; // Default to auto.
103 598 : if (parser.isSet(QLatin1String("range"))) {
104 374 : const QString value = parser.value(QLatin1String("range"));
105 220 : if (value.trimmed().compare(QLatin1String("auto"), Qt::CaseInsensitive) != 0) {
106 140 : switch (settings.mode) {
107 : case MultimeterService::Mode::DcVoltage:
108 : case MultimeterService::Mode::AcVoltage:
109 60 : rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("V"), 50); // mV.
110 60 : break;
111 : case MultimeterService::Mode::DcCurrent:
112 : case MultimeterService::Mode::AcCurrent:
113 40 : rangeOptionValue = parseNumber<std::milli>(value, QLatin1String("A"), 5); // mA.
114 40 : 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 20 : rangeOptionValue = parseNumber<std::nano>(value, QLatin1String("F"), 500); // pF.
120 20 : break;
121 20 : default:
122 48 : qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
123 : }
124 140 : if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
125 26 : errors.append(tr("Invalid range value: %1").arg(value));
126 : }
127 : }
128 154 : }
129 :
130 : // Parse the samples option.
131 598 : if (parser.isSet(QLatin1String("samples"))) {
132 102 : const QString value = parser.value(QLatin1String("samples"));
133 60 : const quint32 samples = parseNumber<std::ratio<1>>(value, QLatin1String("S"));
134 60 : if (samples == 0) {
135 52 : errors.append(tr("Invalid samples value: %1").arg(value));
136 : } else {
137 20 : 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 0 : 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 0 : this, &MeterCommand::outputReading);
198 0 : service->enableReadingNotifications();
199 0 : }
200 :
201 : /*!
202 : * Outputs meter \a reading in the selected ouput format.
203 : */
204 1500 : void MeterCommand::outputReading(const MultimeterService::Reading &reading)
205 : {
206 450 : QString status;
207 1500 : if (reading.status == MultimeterService::MeterStatus::Error) {
208 120 : status = QLatin1String("Error");
209 1380 : } else switch (reading.mode) {
210 : case MultimeterService::Mode::Idle:
211 : break;
212 720 : 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 1224 : ? tr("Auto Range On") : tr("Auto Range Off");
220 720 : break;
221 240 : case MultimeterService::Mode::Continuity:
222 : status = (reading.status == MultimeterService::MeterStatus::Continuity)
223 408 : ? tr("Continuity") : tr("No continuity");
224 240 : break;
225 240 : case MultimeterService::Mode::Temperature:
226 : case MultimeterService::Mode::ExternalTemperature:
227 : case MultimeterService::Mode::Diode:
228 240 : status = tr("Ok");
229 240 : break;
230 : }
231 :
232 1500 : QString unit;
233 1500 : switch (reading.mode) {
234 : case MultimeterService::Mode::Idle: break;
235 120 : case MultimeterService::Mode::DcVoltage: unit = QLatin1String("Vdc"); break;
236 120 : case MultimeterService::Mode::AcVoltage: unit = QLatin1String("Vac"); break;
237 120 : case MultimeterService::Mode::DcCurrent: unit = QLatin1String("Adc"); break;
238 120 : case MultimeterService::Mode::AcCurrent: unit = QLatin1String("Aac"); break;
239 156 : case MultimeterService::Mode::Resistance: unit = QString::fromUtf8("Ω"); break;
240 : case MultimeterService::Mode::Diode: break;
241 : case MultimeterService::Mode::Continuity: break;
242 156 : case MultimeterService::Mode::Temperature: unit = QString::fromUtf8("°C"); break;
243 156 : case MultimeterService::Mode::Capacitance: unit = QString::fromUtf8("F"); break;
244 0 : case MultimeterService::Mode::ExternalTemperature: unit = QString::fromUtf8("°C"); break;
245 : }
246 :
247 2550 : const QString range = service->toString(reading.range, reading.mode);
248 :
249 1500 : switch (format) {
250 : case OutputFormat::Csv:
251 780 : for (; showCsvHeader; showCsvHeader = false) {
252 364 : std::cout << qUtf8Printable(tr("mode,value,unit,status,range\n"));
253 : }
254 1150 : 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 500 : break;
259 150 : case OutputFormat::Json: {
260 : QJsonObject object{
261 150 : { QLatin1String("status"), status },
262 500 : { QLatin1String("value"), qIsInf(reading.value) ?
263 550 : QJsonValue(tr("Infinity")) : QJsonValue(reading.value) },
264 1000 : { QLatin1String("mode"), MultimeterService::toString(reading.mode) },
265 3325 : };
266 500 : if (!unit.isNull()) {
267 406 : object.insert(QLatin1String("unit"), unit);
268 : }
269 500 : if (!range.isNull()) {
270 319 : object.insert(QLatin1String("range"), range);
271 : }
272 1000 : std::cout << QJsonDocument(object).toJson().toStdString();
273 500 : } break;
274 500 : case OutputFormat::Text:
275 800 : std::cout << qUtf8Printable(tr("Mode: %1 (0x%2)\n").arg(MultimeterService::toString(reading.mode))
276 : .arg((quint8)reading.mode,2,16,QLatin1Char('0')));
277 1150 : std::cout << qUtf8Printable(tr("Value: %1 %2\n").arg(reading.value,0,'f').arg(unit));
278 800 : std::cout << qUtf8Printable(tr("Status: %1 (0x%2)\n").arg(status)
279 : .arg((quint8)reading.status,2,16,QLatin1Char('0')));
280 800 : std::cout << qUtf8Printable(tr("Range: %1 (0x%2)\n").arg(range)
281 : .arg((quint8)reading.range,2,16,QLatin1Char('0')));
282 500 : break;
283 : }
284 :
285 1500 : if ((samplesToGo > 0) && (--samplesToGo == 0)) {
286 0 : if (device) disconnect(); // Will exit the application once disconnected.
287 : }
288 2400 : }
|