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 5110 : MeterCommand::MeterCommand(QObject * const parent) : DeviceCommand(parent)
26 2380 : {
27 :
28 5530 : }
29 :
30 2385 : QStringList MeterCommand::requiredOptions(const QCommandLineParser &parser) const
31 2756 : {
32 11713 : return DeviceCommand::requiredOptions(parser) + QStringList{
33 2756 : u"mode"_s,
34 9222 : };
35 2756 : }
36 :
37 1170 : QStringList MeterCommand::supportedOptions(const QCommandLineParser &parser) const
38 1352 : {
39 7878 : return DeviceCommand::supportedOptions(parser) + QStringList{
40 1352 : u"interval"_s,
41 1352 : u"range"_s,
42 1352 : u"samples"_s,
43 6864 : };
44 1352 : }
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 1125 : QStringList MeterCommand::processOptions(const QCommandLineParser &parser)
53 1300 : {
54 2425 : QStringList errors = DeviceCommand::processOptions(parser);
55 2425 : if (!errors.isEmpty()) {
56 52 : return errors;
57 52 : }
58 :
59 : // Parse the (required) mode option.
60 3408 : if (const QString mode = parser.value(u"mode"_s).trimmed().toLower();
61 3912 : mode.startsWith(u"ac v"_s) || mode.startsWith(u"vac"_s)) {
62 97 : settings.mode = MultimeterService::Mode::AcVoltage;
63 97 : minRangeFunc = minVoltageRange;
64 3749 : } else if (mode.startsWith(u"dc v"_s) || mode.startsWith(u"vdc"_s)) {
65 1067 : settings.mode = MultimeterService::Mode::DcVoltage;
66 1067 : minRangeFunc = minVoltageRange;
67 1956 : } else if (mode.startsWith(u"ac c"_s) || mode.startsWith(u"aac"_s)) {
68 97 : settings.mode = MultimeterService::Mode::AcCurrent;
69 97 : minRangeFunc = minCurrentRange;
70 1793 : } else if (mode.startsWith(u"dc c"_s) || mode.startsWith(u"adc"_s)) {
71 194 : settings.mode = MultimeterService::Mode::DcCurrent;
72 194 : minRangeFunc = minCurrentRange;
73 1062 : } else if (mode.startsWith(u"res"_s)) {
74 194 : settings.mode = MultimeterService::Mode::Resistance;
75 194 : minRangeFunc = minResistanceRange;
76 826 : } else if (mode.startsWith(u"dio"_s)) {
77 194 : settings.mode = MultimeterService::Mode::Diode;
78 194 : minRangeFunc = nullptr;
79 590 : } else if (mode.startsWith(u"cont"_s)) {
80 97 : settings.mode = MultimeterService::Mode::Continuity;
81 97 : minRangeFunc = nullptr;
82 472 : } else if (mode.startsWith(u"temp"_s)) {
83 97 : settings.mode = MultimeterService::Mode::Temperature;
84 97 : minRangeFunc = nullptr;
85 354 : } else if (mode.startsWith(u"cap"_s)) {
86 194 : settings.mode = MultimeterService::Mode::Capacitance;
87 194 : minRangeFunc = minCapacitanceRange;
88 104 : } else {
89 148 : errors.append(tr("Unknown meter mode: %1").arg(parser.value(u"mode"_s)));
90 52 : return errors;
91 628 : }
92 :
93 : // Parse the interval option.
94 2714 : if (parser.isSet(u"interval"_s)) {
95 590 : const QString value = parser.value(u"interval"_s);
96 485 : const quint32 interval = parseNumber<std::milli>(value, u"s"_s, 500);
97 485 : if (interval == 0) {
98 236 : errors.append(tr("Invalid interval value: %1").arg(value));
99 156 : } else {
100 291 : settings.updateInterval = interval;
101 156 : }
102 380 : }
103 :
104 : // Parse the range option.
105 2231 : rangeOptionValue = 0; // Default to auto.
106 2714 : if (parser.isSet(u"range"_s)) {
107 1298 : const QString value = parser.value(u"range"_s);
108 1298 : if (value.trimmed().compare(u"auto"_s, Qt::CaseInsensitive) != 0) {
109 679 : switch (settings.mode) {
110 239 : case MultimeterService::Mode::DcVoltage:
111 156 : case MultimeterService::Mode::AcVoltage:
112 291 : rangeOptionValue = parseNumber<std::milli>(value, u"V"_s, 50); // mV.
113 291 : break;
114 142 : case MultimeterService::Mode::DcCurrent:
115 104 : case MultimeterService::Mode::AcCurrent:
116 194 : rangeOptionValue = parseNumber<std::milli>(value, u"A"_s, 5); // mA.
117 194 : break;
118 0 : case MultimeterService::Mode::Resistance:
119 0 : rangeOptionValue = parseNumber<std::ratio<1>>(value, u"ohms"_s);
120 0 : break;
121 97 : case MultimeterService::Mode::Capacitance:
122 97 : rangeOptionValue = parseNumber<std::nano>(value, u"F"_s, 500); // pF.
123 97 : break;
124 97 : default:
125 176 : qCInfo(lc).noquote() << tr("Ignoring range value: %1").arg(value);
126 364 : }
127 679 : if ((minRangeFunc != nullptr) && (rangeOptionValue == 0)) {
128 118 : errors.append(tr("Invalid range value: %1").arg(value));
129 52 : }
130 364 : }
131 836 : }
132 :
133 : // Parse the samples option.
134 2714 : if (parser.isSet(u"samples"_s)) {
135 354 : const QString value = parser.value(u"samples"_s);
136 291 : const quint32 samples = parseNumber<std::ratio<1>>(value, u"S"_s);
137 291 : if (samples == 0) {
138 236 : errors.append(tr("Invalid samples value: %1").arg(value));
139 104 : } else {
140 97 : samplesToGo = samples;
141 52 : }
142 228 : }
143 1196 : return errors;
144 1196 : }
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 3375 : void MeterCommand::outputReading(const MultimeterService::Reading &reading)
208 1650 : {
209 3225 : QString status;
210 5025 : if (reading.status == MultimeterService::MeterStatus::Error) {
211 402 : status = u"Error"_s;
212 4623 : } else switch (reading.mode) {
213 198 : case MultimeterService::Mode::Idle:
214 198 : break;
215 1752 : case MultimeterService::Mode::DcVoltage:
216 264 : case MultimeterService::Mode::AcVoltage:
217 396 : case MultimeterService::Mode::DcCurrent:
218 528 : case MultimeterService::Mode::AcCurrent:
219 660 : case MultimeterService::Mode::Resistance:
220 792 : case MultimeterService::Mode::Capacitance:
221 792 : status = (reading.status == MultimeterService::MeterStatus::AutoRangeOn)
222 3276 : ? tr("Auto Range On") : tr("Auto Range Off");
223 2412 : break;
224 804 : case MultimeterService::Mode::Continuity:
225 264 : status = (reading.status == MultimeterService::MeterStatus::Continuity)
226 1092 : ? tr("Continuity") : tr("No continuity");
227 804 : break;
228 672 : case MultimeterService::Mode::Temperature:
229 132 : case MultimeterService::Mode::ExternalTemperature:
230 264 : case MultimeterService::Mode::Diode:
231 552 : status = tr("Ok");
232 804 : break;
233 1518 : }
234 :
235 5025 : QString unit;
236 5025 : switch (reading.mode) {
237 330 : case MultimeterService::Mode::Idle: break;
238 528 : case MultimeterService::Mode::DcVoltage: unit = u"Vdc"_s; break;
239 528 : case MultimeterService::Mode::AcVoltage: unit = u"Vac"_s; break;
240 528 : case MultimeterService::Mode::DcCurrent: unit = u"Adc"_s; break;
241 528 : case MultimeterService::Mode::AcCurrent: unit = u"Aac"_s; break;
242 528 : case MultimeterService::Mode::Resistance: unit = QString::fromUtf8("Ω"); break;
243 132 : case MultimeterService::Mode::Diode: break;
244 264 : case MultimeterService::Mode::Continuity: break;
245 528 : case MultimeterService::Mode::Temperature: unit = QString::fromUtf8("°C"); break;
246 528 : case MultimeterService::Mode::Capacitance: unit = QString::fromUtf8("F"); break;
247 0 : case MultimeterService::Mode::ExternalTemperature: unit = QString::fromUtf8("°C"); break;
248 1650 : }
249 :
250 6825 : const QString range = service->toString(reading.range, reading.mode);
251 :
252 5025 : switch (format) {
253 550 : case OutputFormat::Csv:
254 2613 : for (; showCsvHeader; showCsvHeader = false) {
255 1232 : std::cout << qUtf8Printable(tr("mode,value,unit,status,range\n"));
256 308 : }
257 3850 : std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4,%5\n")
258 550 : .arg(escapeCsvField(MultimeterService::toString(reading.mode)))
259 550 : .arg(reading.value, 0, 'f').arg(unit, status, range)
260 550 : );
261 1675 : break;
262 1675 : case OutputFormat::Json: {
263 550 : QJsonObject object{
264 2275 : { u"status"_s, status },
265 2200 : { u"value"_s, qIsInf(reading.value) ?
266 1725 : QJsonValue(tr("Infinity")) : QJsonValue(reading.value) },
267 3400 : { u"mode"_s, MultimeterService::toString(reading.mode) },
268 6525 : };
269 1675 : if (!unit.isNull()) {
270 1736 : object.insert(u"unit"_s, unit);
271 308 : }
272 1675 : if (!range.isNull()) {
273 1364 : object.insert(u"range"_s, range);
274 242 : }
275 2800 : std::cout << QJsonDocument(object).toJson().toStdString();
276 1675 : } break;
277 1675 : case OutputFormat::Text:
278 2800 : std::cout << qUtf8Printable(tr("Mode: %1 (0x%2)\n").arg(MultimeterService::toString(reading.mode))
279 550 : .arg((quint8)reading.mode,2,16,'0'_L1));
280 3325 : std::cout << qUtf8Printable(tr("Value: %1 %2\n").arg(reading.value,0,'f').arg(unit));
281 2725 : std::cout << qUtf8Printable(tr("Status: %1 (0x%2)\n").arg(status)
282 550 : .arg((quint8)reading.status,2,16,'0'_L1));
283 2650 : std::cout << qUtf8Printable(tr("Range: %1 (0x%2)\n").arg(range)
284 550 : .arg((quint8)reading.range,2,16,'0'_L1));
285 1675 : break;
286 1650 : }
287 :
288 5025 : if ((samplesToGo > 0) && (--samplesToGo == 0)) {
289 0 : if (device) disconnect(); // Will exit the application once disconnected.
290 0 : }
291 9375 : }
|