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 : #include "../stringliterals_p.h"
6 :
7 : #include <qtpokit/pokitdevice.h>
8 :
9 : #include <QJsonDocument>
10 : #include <QJsonObject>
11 :
12 : #include <iostream>
13 :
14 : DOKIT_USE_STRINGLITERALS
15 :
16 : /*!
17 : * \class MeterCommand
18 : *
19 : * The MeterCommand class implements the `meter` CLI command.
20 : */
21 :
22 : /*!
23 : * Construct a new MeterCommand object with \a parent.
24 : */
25 7126 : MeterCommand::MeterCommand(QObject * const parent) : DeviceCommand(parent)
26 3024 : {
27 :
28 7574 : }
29 :
30 3445 : QStringList MeterCommand::requiredOptions(const QCommandLineParser &parser) const
31 3816 : {
32 16324 : return DeviceCommand::requiredOptions(parser) + QStringList{
33 3816 : u"mode"_s,
34 12614 : };
35 3816 : }
36 :
37 1690 : QStringList MeterCommand::supportedOptions(const QCommandLineParser &parser) const
38 1872 : {
39 11284 : return DeviceCommand::supportedOptions(parser) + QStringList{
40 1872 : u"interval"_s,
41 1872 : u"range"_s,
42 1872 : u"samples"_s,
43 9568 : };
44 1872 : }
45 :
46 : /*!
47 : * \copybrief DeviceCommand::processOptions
48 : *
49 : * This implementation extends DeviceCommand::processOptions to process additional CLI options
50 : * supported (or required) by this command.
51 : */
52 1625 : QStringList MeterCommand::processOptions(const QCommandLineParser &parser)
53 1800 : {
54 3425 : QStringList errors = DeviceCommand::processOptions(parser);
55 3425 : if (!errors.isEmpty()) {
56 72 : return errors;
57 72 : }
58 :
59 : // Parse the (required) mode option.
60 4848 : if (const QString mode = parser.value(u"mode"_s).trimmed().toLower();
61 5832 : mode.startsWith(u"ac v"_s) || mode.startsWith(u"vac"_s)) {
62 137 : settings.mode = MultimeterService::Mode::AcVoltage;
63 137 : minRangeFunc = minVoltageRange;
64 5589 : } else if (mode.startsWith(u"dc v"_s) || mode.startsWith(u"vdc"_s)) {
65 1507 : settings.mode = MultimeterService::Mode::DcVoltage;
66 1507 : minRangeFunc = minVoltageRange;
67 2916 : } else if (mode.startsWith(u"ac c"_s) || mode.startsWith(u"aac"_s)) {
68 137 : settings.mode = MultimeterService::Mode::AcCurrent;
69 137 : minRangeFunc = minCurrentRange;
70 2673 : } else if (mode.startsWith(u"dc c"_s) || mode.startsWith(u"adc"_s)) {
71 274 : settings.mode = MultimeterService::Mode::DcCurrent;
72 274 : minRangeFunc = minCurrentRange;
73 1602 : } else if (mode.startsWith(u"res"_s)) {
74 274 : settings.mode = MultimeterService::Mode::Resistance;
75 274 : minRangeFunc = minResistanceRange;
76 1246 : } else if (mode.startsWith(u"dio"_s)) {
77 274 : settings.mode = MultimeterService::Mode::Diode;
78 274 : minRangeFunc = nullptr;
79 890 : } else if (mode.startsWith(u"cont"_s)) {
80 137 : settings.mode = MultimeterService::Mode::Continuity;
81 137 : minRangeFunc = nullptr;
82 712 : } else if (mode.startsWith(u"temp"_s)) {
83 137 : settings.mode = MultimeterService::Mode::Temperature;
84 137 : minRangeFunc = nullptr;
85 534 : } else if (mode.startsWith(u"cap"_s)) {
86 274 : settings.mode = MultimeterService::Mode::Capacitance;
87 274 : minRangeFunc = minCapacitanceRange;
88 144 : } else {
89 215 : errors.append(tr("Unknown meter mode: %1").arg(parser.value(u"mode"_s)));
90 72 : return errors;
91 648 : }
92 :
93 : // Parse the interval option.
94 4094 : if (parser.isSet(u"interval"_s)) {
95 935 : const QString value = parser.value(u"interval"_s);
96 685 : const quint32 interval = parseNumber<std::milli>(value, u"s"_s, 500);
97 685 : if (interval == 0) {
98 356 : errors.append(tr("Invalid interval value: %1").arg(value));
99 216 : } else {
100 411 : settings.updateInterval = interval;
101 216 : }
102 480 : }
103 :
104 : // Parse the range option.
105 3151 : rangeOptionValue = 0; // Default to auto.
106 4094 : if (parser.isSet(u"range"_s)) {
107 2057 : const QString value = parser.value(u"range"_s);
108 1958 : if (value.trimmed().compare(u"auto"_s, Qt::CaseInsensitive) != 0) {
109 959 : switch (settings.mode) {
110 339 : case MultimeterService::Mode::DcVoltage:
111 216 : case MultimeterService::Mode::AcVoltage:
112 411 : rangeOptionValue = parseNumber<std::milli>(value, u"V"_s, 50); // mV.
113 411 : break;
114 202 : case MultimeterService::Mode::DcCurrent:
115 144 : case MultimeterService::Mode::AcCurrent:
116 274 : rangeOptionValue = parseNumber<std::milli>(value, u"A"_s, 5); // mA.
117 274 : break;
118 0 : case MultimeterService::Mode::Resistance:
119 0 : rangeOptionValue = parseNumber<std::ratio<1>>(value, u"ohms"_s);
120 0 : break;
121 137 : case MultimeterService::Mode::Capacitance:
122 137 : rangeOptionValue = parseNumber<std::nano>(value, u"F"_s, 500); // pF.
123 137 : break;
124 137 : default:
125 272 : qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
126 504 : }
127 959 : if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
128 178 : errors.append(tr("Invalid range value: %1").arg(value));
129 72 : }
130 504 : }
131 1056 : }
132 :
133 : // Parse the samples option.
134 4094 : if (parser.isSet(u"samples"_s)) {
135 561 : const QString value = parser.value(u"samples"_s);
136 411 : const quint32 samples = parseNumber<std::ratio<1>>(value, u"S"_s);
137 411 : if (samples == 0) {
138 356 : errors.append(tr("Invalid samples value: %1").arg(value));
139 144 : } else {
140 137 : samplesToGo = samples;
141 72 : }
142 288 : }
143 1656 : return errors;
144 1656 : }
145 :
146 : /*!
147 : * \copybrief DeviceCommand::getService
148 : *
149 : * This override returns a pointer to a MultimeterService object.
150 : */
151 0 : AbstractPokitService * MeterCommand::getService()
152 0 : {
153 0 : Q_ASSERT(device);
154 0 : if (!service) {
155 0 : service = device->multimeter();
156 0 : Q_ASSERT(service);
157 0 : connect(service, &MultimeterService::settingsWritten,
158 0 : this, &MeterCommand::settingsWritten);
159 0 : }
160 0 : return service;
161 0 : }
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 0 : {
170 0 : DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
171 0 : settings.range = (minRangeFunc == nullptr) ? 0 : minRangeFunc(*service->pokitProduct(), rangeOptionValue);
172 0 : const QString range = service->toString(settings.range, settings.mode);
173 0 : qCInfo(lc).noquote() << tr("Measuring %1, with range %2, every %L3ms.").arg(
174 0 : MultimeterService::toString(settings.mode),
175 0 : (range.isNull()) ? QString::fromLatin1("N/A") : range).arg(settings.updateInterval);
176 0 : service->setSettings(settings);
177 0 : }
178 :
179 : /*!
180 : * \var MeterCommand::minRangeFunc
181 : *
182 : * Pointer to function for converting #rangeOptionValue to a Pokit device's range enumerator. This function pointer
183 : * is assigned during the command line parsing, but is not invoked until after the device's services are discovered,
184 : * because prior to that discovery, we don't know which product (Meter vs Pro vs Clamp, etc) we're talking to and thus
185 : * which enumerator list to be using.
186 : *
187 : * If the current mode does not support ranges (eg diode, and continuity modes), then this member will be \c nullptr.
188 : *
189 : * \see processOptions
190 : * \see serviceDetailsDiscovered
191 : */
192 :
193 : /*!
194 : * Invoked when the multimeter settings have been written, to begin reading the meter values.
195 : */
196 0 : void MeterCommand::settingsWritten()
197 0 : {
198 0 : qCDebug(lc).noquote() << tr("Settings written; starting meter readings...");
199 0 : connect(service, &MultimeterService::readingRead,
200 0 : this, &MeterCommand::outputReading);
201 0 : service->enableReadingNotifications();
202 0 : }
203 :
204 : /*!
205 : * Outputs meter \a reading in the selected output format.
206 : */
207 4875 : void MeterCommand::outputReading(const MultimeterService::Reading &reading)
208 1800 : {
209 4875 : QString status;
210 6675 : if (reading.status == MultimeterService::MeterStatus::Error) {
211 534 : status = u"Error"_s;
212 6141 : } else switch (reading.mode) {
213 216 : case MultimeterService::Mode::Idle:
214 216 : break;
215 2484 : case MultimeterService::Mode::DcVoltage:
216 288 : case MultimeterService::Mode::AcVoltage:
217 432 : case MultimeterService::Mode::DcCurrent:
218 576 : case MultimeterService::Mode::AcCurrent:
219 720 : case MultimeterService::Mode::Resistance:
220 864 : case MultimeterService::Mode::Capacitance:
221 864 : status = (reading.status == MultimeterService::MeterStatus::AutoRangeOn)
222 4068 : ? tr("Auto Range On") : tr("Auto Range Off");
223 3204 : break;
224 1068 : case MultimeterService::Mode::Continuity:
225 288 : status = (reading.status == MultimeterService::MeterStatus::Continuity)
226 1356 : ? tr("Continuity") : tr("No continuity");
227 1068 : break;
228 924 : case MultimeterService::Mode::Temperature:
229 144 : case MultimeterService::Mode::ExternalTemperature:
230 288 : case MultimeterService::Mode::Diode:
231 720 : status = tr("Ok");
232 1068 : break;
233 1656 : }
234 :
235 6675 : QString unit;
236 6675 : switch (reading.mode) {
237 360 : case MultimeterService::Mode::Idle: break;
238 780 : case MultimeterService::Mode::DcVoltage: unit = u"Vdc"_s; break;
239 780 : case MultimeterService::Mode::AcVoltage: unit = u"Vac"_s; break;
240 780 : case MultimeterService::Mode::DcCurrent: unit = u"Adc"_s; break;
241 780 : case MultimeterService::Mode::AcCurrent: unit = u"Aac"_s; break;
242 780 : case MultimeterService::Mode::Resistance: unit = QString::fromUtf8("Ω"); break;
243 144 : case MultimeterService::Mode::Diode: break;
244 288 : case MultimeterService::Mode::Continuity: break;
245 780 : case MultimeterService::Mode::Temperature: unit = QString::fromUtf8("°C"); break;
246 780 : case MultimeterService::Mode::Capacitance: unit = QString::fromUtf8("F"); break;
247 0 : case MultimeterService::Mode::ExternalTemperature: unit = QString::fromUtf8("°C"); break;
248 1800 : }
249 :
250 8475 : const QString range = service->toString(reading.range, reading.mode);
251 :
252 6675 : switch (format) {
253 600 : case OutputFormat::Csv:
254 3471 : for (; showCsvHeader; showCsvHeader = false) {
255 1820 : std::cout << qUtf8Printable(tr("mode,value,unit,status,range\n"));
256 336 : }
257 5650 : std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4,%5\n")
258 600 : .arg(escapeCsvField(MultimeterService::toString(reading.mode)))
259 600 : .arg(reading.value, 0, 'f').arg(unit, status, range)
260 600 : );
261 2225 : break;
262 2225 : case OutputFormat::Json: {
263 600 : QJsonObject object{
264 2600 : { u"status"_s, status },
265 3475 : { u"value"_s, qIsInf(reading.value) ?
266 2275 : QJsonValue(tr("Infinity")) : QJsonValue(reading.value) },
267 4225 : { u"mode"_s, MultimeterService::toString(reading.mode) },
268 9875 : };
269 2225 : if (!unit.isNull()) {
270 2506 : object.insert(u"unit"_s, unit);
271 336 : }
272 2225 : if (!range.isNull()) {
273 1969 : object.insert(u"range"_s, range);
274 264 : }
275 3850 : std::cout << QJsonDocument(object).toJson().toStdString();
276 2225 : } break;
277 2225 : case OutputFormat::Text:
278 4525 : std::cout << qUtf8Printable(tr("Mode: %1 (0x%2)\n").arg(MultimeterService::toString(reading.mode))
279 600 : .arg((quint8)reading.mode,2,16,'0'_L1));
280 4975 : std::cout << qUtf8Printable(tr("Value: %1 %2\n").arg(reading.value,0,'f').arg(unit));
281 4275 : std::cout << qUtf8Printable(tr("Status: %1 (0x%2)\n").arg(status)
282 600 : .arg((quint8)reading.status,2,16,'0'_L1));
283 4075 : std::cout << qUtf8Printable(tr("Range: %1 (0x%2)\n").arg(range)
284 600 : .arg((quint8)reading.range,2,16,'0'_L1));
285 2225 : break;
286 1800 : }
287 :
288 6675 : if ((samplesToGo > 0) && (--samplesToGo == 0)) {
289 0 : if (device) disconnect(); // Will exit the application once disconnected.
290 0 : }
291 10225 : }
|