Line data Source code
1 : // SPDX-FileCopyrightText: 2022-2026 Paul Colby <git@colby.id.au>
2 : // SPDX-License-Identifier: LGPL-3.0-or-later
3 :
4 : #include "dsocommand.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 DsoCommand
18 : *
19 : * The DsoCommand class implements the `dso` CLI command.
20 : */
21 :
22 : /*!
23 : * Construct a new DsoCommand object with \a parent.
24 : */
25 17142 : DsoCommand::DsoCommand(QObject * const parent) : DeviceCommand(parent)
26 6224 : {
27 :
28 17142 : }
29 :
30 17613 : QStringList DsoCommand::requiredOptions(const QCommandLineParser &parser) const
31 9792 : {
32 76482 : return DeviceCommand::requiredOptions(parser) + QStringList{
33 9792 : u"mode"_s,
34 9792 : u"range"_s,
35 74088 : };
36 9792 : }
37 :
38 8755 : QStringList DsoCommand::supportedOptions(const QCommandLineParser &parser) const
39 4832 : {
40 62802 : return DeviceCommand::supportedOptions(parser) + QStringList{
41 4832 : u"interval"_s,
42 4832 : u"samples"_s,
43 4832 : u"sample-rate"_s,
44 4832 : u"trigger-level"_s,
45 4832 : u"trigger-mode"_s,
46 4832 : u"window-size"_s,
47 71812 : };
48 4832 : }
49 :
50 : /*!
51 : * \copybrief DeviceCommand::processOptions
52 : *
53 : * This implementation extends DeviceCommand::processOptions to process additional CLI options
54 : * supported (or required) by this command.
55 : */
56 8652 : QStringList DsoCommand::processOptions(const QCommandLineParser &parser)
57 4704 : {
58 13356 : QStringList errors = DeviceCommand::processOptions(parser);
59 13356 : if (!errors.isEmpty()) {
60 336 : return errors;
61 336 : }
62 :
63 : // Parse the (required) mode option.
64 20748 : if (const QString mode = parser.value(u"mode"_s).trimmed().toLower();
65 25896 : mode.startsWith(u"ac v"_s) || mode.startsWith(u"vac"_s)) {
66 318 : settings.mode = DsoService::Mode::AcVoltage;
67 25232 : } else if (mode.startsWith(u"dc v"_s) || mode.startsWith(u"vdc"_s)) {
68 11130 : settings.mode = DsoService::Mode::DcVoltage;
69 5576 : } else if (mode.startsWith(u"ac c"_s) || mode.startsWith(u"aac"_s)) {
70 318 : settings.mode = DsoService::Mode::AcCurrent;
71 1328 : } else if (mode.startsWith(u"dc c"_s) || mode.startsWith(u"adc"_s)) {
72 318 : settings.mode = DsoService::Mode::DcCurrent;
73 112 : } else {
74 578 : errors.append(tr("Unknown DSO mode: %1").arg(parser.value(u"mode"_s)));
75 112 : return errors;
76 2686 : }
77 :
78 : // Parse the (required) range option.
79 9576 : QString unit;
80 4256 : {
81 12084 : const QString value = parser.value(u"range"_s);
82 4256 : quint32 sensibleMinimum = 0;
83 12084 : switch (settings.mode) {
84 0 : case DsoService::Mode::Idle:
85 0 : Q_ASSERT(false); // Not possible, since the mode parsing above never allows Idle.
86 0 : break;
87 11336 : case DsoService::Mode::DcVoltage:
88 4032 : case DsoService::Mode::AcVoltage:
89 11448 : minRangeFunc = minVoltageRange;
90 11448 : unit = u"V"_s;
91 4032 : sensibleMinimum = 50; // mV.
92 11448 : break;
93 524 : case DsoService::Mode::DcCurrent:
94 224 : case DsoService::Mode::AcCurrent:
95 636 : minRangeFunc = minCurrentRange;
96 636 : unit = u"A"_s;
97 224 : sensibleMinimum = 5; // mA.
98 636 : break;
99 4256 : }
100 4256 : Q_ASSERT(!unit.isEmpty());
101 12084 : rangeOptionValue = parseNumber<std::milli>(value, unit, sensibleMinimum);
102 12084 : if (rangeOptionValue == 0) {
103 458 : errors.append(tr("Invalid range value: %1").arg(value));
104 112 : }
105 6764 : }
106 :
107 : // Parse the trigger-level option.
108 17404 : if (parser.isSet(u"trigger-level"_s)) {
109 896 : float sign = 1.0;
110 3808 : const QString rawValue = parser.value(u"trigger-level"_s);
111 896 : QString absValue = rawValue;
112 896 : DOKIT_STRING_INDEX_TYPE nonSpacePos;
113 2544 : for (nonSpacePos = 0; (nonSpacePos < rawValue.length()) && (rawValue.at(nonSpacePos) == u' '); ++nonSpacePos);
114 2544 : if ((nonSpacePos < rawValue.length()) && (rawValue.at(nonSpacePos) == u'-')) {
115 0 : absValue = rawValue.mid(nonSpacePos+1);
116 0 : sign = -1.0;
117 0 : }
118 2544 : const float level = parseNumber<std::ratio<1>,float>(absValue, unit, 0.f);
119 3424 : qCDebug(lc) << "Trigger level" << rawValue << absValue << nonSpacePos << sign << level;
120 2544 : if (qIsNaN(level)) {
121 458 : errors.append(tr("Invalid trigger-level value: %1").arg(rawValue));
122 784 : } else {
123 2226 : settings.triggerLevel = sign * level;
124 2996 : qCDebug(lc) << "Trigger level" << settings.triggerLevel;
125 : // Check the trigger level is within the Votage / Current range.
126 3668 : if ((rangeOptionValue != 0) && (qAbs(settings.triggerLevel) > (rangeOptionValue/1000.0))) {
127 0 : errors.append(tr("Trigger-level %1%2 is outside range ±%3%2").arg(
128 0 : appendSiPrefix(settings.triggerLevel), unit, appendSiPrefix(rangeOptionValue / 1000.0)));
129 0 : }
130 784 : }
131 1424 : }
132 :
133 : // Parse the trigger-mode option.
134 17404 : if (parser.isSet(u"trigger-mode"_s)) {
135 4192 : const QString triggerMode = parser.value(u"trigger-mode"_s).trimmed().toLower();
136 3664 : if (triggerMode.startsWith(u"free"_s)) {
137 954 : settings.command = DsoService::Command::FreeRunning;
138 2290 : } else if (triggerMode.startsWith(u"ris"_s)) {
139 636 : settings.command = DsoService::Command::RisingEdgeTrigger;
140 1374 : } else if (triggerMode.startsWith(u"fall"_s)) {
141 636 : settings.command = DsoService::Command::FallingEdgeTrigger;
142 224 : } else {
143 578 : errors.append(tr("Unknown trigger mode: %1").arg(parser.value(u"trigger-mode"_s)));
144 112 : }
145 1424 : }
146 :
147 : // Ensure that if either trigger option is present, then both are.
148 25232 : if (parser.isSet(u"trigger-level"_s) != parser.isSet(u"trigger-mode"_s)) {
149 636 : errors.append(tr("If either option is provided, then both must be: trigger-level, trigger-mode"));
150 224 : }
151 :
152 : // Parse the sample-rate option.
153 17404 : if (parser.isSet(u"sample-rate"_s)) {
154 17136 : const QString value = parser.value(u"sample-rate"_s);
155 11448 : sampleRateValue = parseNumber<std::ratio<1,1>>(value, u"Hz"_s, (quint32)50'000);
156 11448 : if (sampleRateValue == 0) {
157 916 : errors.append(tr("Invalid sample-rate value: %1").arg(value));
158 10812 : } else if (sampleRateValue > 1'000'000) {
159 774 : qCWarning(lc).noquote() << tr("Pokit devices do not officially support sample rates greater than 1Mhz");
160 112 : }
161 6408 : }
162 :
163 : // Parse the interval option.
164 17404 : if (parser.isSet(u"interval"_s)) {
165 7140 : const QString value = parser.value(u"interval"_s);
166 4770 : const quint32 interval = parseNumber<std::micro>(value, u"s"_s, (quint32)500'000);
167 4770 : if (interval == 0) {
168 916 : errors.append(tr("Invalid interval value: %1").arg(value));
169 1456 : } else {
170 4134 : settings.samplingWindow = interval;
171 1456 : }
172 2670 : }
173 :
174 : // Parse the window-size option.
175 17404 : if (parser.isSet(u"window-size"_s)) {
176 3808 : const QString value = parser.value(u"window-size"_s);
177 2544 : const quint32 samples = parseNumber<std::ratio<1>>(value, u"S"_s);
178 2544 : if (samples == 0) {
179 916 : errors.append(tr("Invalid window-size value: %1").arg(value));
180 1908 : } else if (samples > std::numeric_limits<quint16>::max()) {
181 0 : errors.append(tr("Window size value (%1) must be no greater than %2")
182 0 : .arg(value).arg(std::numeric_limits<quint16>::max()));
183 672 : } else {
184 1908 : settings.numberOfSamples = (quint16)samples;
185 1908 : if (const auto maxSamples = maxWindowSize(PokitProduct::PokitPro); settings.numberOfSamples > maxSamples) {
186 634 : qCWarning(lc).noquote() <<
187 458 : tr("No Pokit device officially supports windows greater than %L1 samples").arg(maxSamples);
188 112 : }
189 672 : }
190 1424 : }
191 :
192 : // Ensure that we have at least: sample-rate, or both interval and window-size.
193 13122 : if ((!parser.isSet(u"sample-rate"_s)) && !(parser.isSet(u"interval"_s) && parser.isSet(u"window-size"_s))) {
194 318 : errors.append(tr("Missing required option/s: either sample-rate, or both interval and window-size"));
195 112 : }
196 :
197 : // If we have all three sample-rate related options, ensure they agree.
198 12084 : if ((sampleRateValue != 0) && (settings.numberOfSamples != 0) && (settings.samplingWindow != 0)) {
199 636 : const quint32 sampleRate = settings.numberOfSamples * 1'000'000ull / settings.samplingWindow;
200 636 : if (sampleRate != sampleRateValue) {
201 384 : errors.append(tr("Windows size (%1 samples) and interval (%2ns) yield a sample rate of %3Hz, which does "
202 112 : "not match the supplied sample-rate (%4Hz). Tip: leave one option unset to have dokit calculate the "
203 412 : "remaining option.").arg(settings.numberOfSamples).arg(settings.samplingWindow).arg(sampleRate)
204 506 : .arg(sampleRateValue));
205 112 : }
206 224 : }
207 :
208 : // Parse the samples option.
209 17404 : if (parser.isSet(u"samples"_s)) {
210 1428 : const QString value = parser.value(u"samples"_s);
211 954 : samplesValue = parseNumber<std::ratio<1>>(value, u"S"_s);
212 954 : if (samplesValue == 0) {
213 916 : errors.append(tr("Invalid samples value: %1").arg(value));
214 224 : }
215 534 : }
216 4256 : return errors;
217 6764 : }
218 :
219 : /*!
220 : * \copybrief DeviceCommand::getService
221 : *
222 : * This override returns a pointer to a DsoService object.
223 : */
224 0 : AbstractPokitService * DsoCommand::getService()
225 0 : {
226 0 : Q_ASSERT(device);
227 0 : if (!service) {
228 0 : service = device->dso();
229 0 : Q_ASSERT(service);
230 0 : connect(service, &DsoService::metadataRead, this, &DsoCommand::metadataRead);
231 0 : connect(service, &DsoService::samplesRead, this, &DsoCommand::outputSamples);
232 0 : connect(service, &DsoService::settingsWritten, this, &DsoCommand::settingsWritten);
233 0 : }
234 0 : return service;
235 0 : }
236 :
237 : /*!
238 : * Returns the \a product's maximum sampling window size.
239 : *
240 : * \pokitApi Pokit's official documentation claim the maximum is 8,192. However, my Pokit Meter fails for window size
241 : * greater than 8,191, while my Pokit Pro supports up to 16,384 samples per window.
242 : */
243 2190 : quint16 DsoCommand::maxWindowSize(const PokitProduct product)
244 3488 : {
245 5678 : switch (product) {
246 1408 : case PokitProduct::PokitMeter:
247 1408 : return 8'191;
248 3957 : case PokitProduct::PokitPro:
249 3957 : return 16'384;
250 3488 : }
251 2140 : Q_ASSERT_X(false, "DsoCommand::maxWindowSize", "Unknown PokitProduct enum value");
252 0 : return 0;
253 3488 : }
254 :
255 : /*!
256 : * Configures the \a settings.numberOfSamples and/or \a settings.samplingWindow, if not already set, according to the
257 : * requested \a sampleRate. The chosen \a settings will be limited to \a product's capaibilities.
258 : *
259 : * Returns \c true os settings were set (either by this function, or they were already set), or \c false if the
260 : * settings could not be determined succesfully (eg, because \a sampleRate was too high for the \a product).
261 : */
262 2060 : bool DsoCommand::configureWindow(const PokitProduct product, const quint32 sampleRate, DsoService::Settings &settings)
263 2560 : {
264 2560 : const quint32 maxSampleRate = 1'000'000; // Pokit Meter and Pokit Pro both sample up to 1MHz.
265 4620 : const quint32 maxWindowSize = DsoCommand::maxWindowSize(product);
266 :
267 4620 : if (sampleRate > maxSampleRate) {
268 0 : qCWarning(lc).noquote() <<
269 0 : tr("The requested sample rate (%1Hz) likely exceeds the connected device's limit (%2Hz)")
270 0 : .arg(sampleRate).arg(maxSampleRate);
271 0 : }
272 :
273 4620 : if (settings.numberOfSamples > maxWindowSize) {
274 0 : qCWarning(lc).noquote() <<
275 0 : tr("Requested window size (%1 samples) likely exceeds the connected device's limit (%2) samples")
276 0 : .arg(settings.samplingWindow).arg(maxWindowSize);
277 0 : }
278 :
279 4620 : if ((settings.numberOfSamples != 0) && (settings.samplingWindow != 0)) {
280 1144 : qCDebug(lc).noquote() << "Both numberOfSamples and samplingWindow are set, so no need to derive either";
281 924 : if (sampleRate != 0) {
282 924 : const quint32 derivedRate = settings.numberOfSamples * 1'000'000ull / settings.samplingWindow;
283 1144 : qCDebug(lc).noquote() << "derivedRate" << derivedRate << sampleRate;
284 924 : if (sampleRate != derivedRate) {
285 918 : qCWarning(lc).noquote() << tr("Ignoring sample-rate, as interval and window-size both provided");
286 256 : }
287 512 : }
288 924 : return true; // Nothing more to do.
289 512 : }
290 2376 : Q_ASSERT_X(sampleRate > 0, "DsoCommand::configureWindow", "processOptions should have rejected already");
291 :
292 : // If both window parameters are unset, choose the best window size (we'll choose a window period later).
293 3696 : if ((settings.numberOfSamples == 0) && (settings.samplingWindow == 0)) {
294 1716 : qCDebug(lc).noquote() << tr("Choosing best number-of-samples for sample-rate %2Hz").arg(sampleRate);
295 768 : double smallestDifference = std::numeric_limits<double>::quiet_NaN();
296 17031861 : for (quint32 windowSize = maxWindowSize; windowSize > 0; --windowSize) {
297 17030475 : const quint32 period = windowSize * 1'000'000ull / sampleRate;
298 17030475 : const double effectiveRate = double(windowSize) * 1'000'000.0 / (double)period;
299 17030475 : if (effectiveRate > maxSampleRate) continue; // Skip sizes that would exceed the device's max sample rate.
300 17030475 : if (const quint32 effectivePeriod = windowSize * 1'000'000ull / sampleRate;
301 11967407 : effectivePeriod > 1'000'000) continue; // Skip sizes that would take longer than 1s to fetch.
302 11355036 : const double difference = qAbs(effectiveRate - sampleRate);
303 : // qCDebug(lc).noquote() << tr("%1 samples, %2us, %3Hz, %4Hz, ±%5Hz").arg(windowSize).arg(period)
304 : // .arg(effectiveRate, 0, 'f').arg(sampleRate).arg(difference, 0, 'f');
305 11355036 : if ((settings.numberOfSamples == 0) || (difference < smallestDifference)) {
306 4158 : settings.numberOfSamples = windowSize;
307 2304 : smallestDifference = difference;
308 2304 : }
309 6291968 : }
310 1716 : qCDebug(lc).noquote() << tr("Chose %Ln sample/s, with error ±%2Hz", nullptr,
311 0 : settings.numberOfSamples).arg(smallestDifference, 0, 'f');
312 1386 : if (settings.numberOfSamples == 0) {
313 0 : qCCritical(lc).noquote() << tr("Failed to select a compatible window size for sample rate %1Hz").arg(sampleRate);
314 0 : return false;
315 0 : }
316 768 : }
317 :
318 3696 : if (settings.numberOfSamples == 0) {
319 2288 : qCDebug(lc).noquote() << tr("Calculating number-of-samples for %1us window at %2Hz")
320 0 : .arg(settings.samplingWindow).arg(sampleRate);
321 1024 : Q_ASSERT(settings.samplingWindow != 0);
322 1848 : const auto numberOfSamples = sampleRate * settings.samplingWindow / 1'000'000ull;
323 2288 : qCDebug(lc).noquote() << tr("Calculated %Ln sample/s", nullptr, numberOfSamples);
324 1848 : if ((numberOfSamples == 0) || (numberOfSamples > maxWindowSize)) {
325 826 : qCCritical(lc).noquote() << tr("Failed to calculate a valid number of samples for a %L1us period at %2Hz")
326 650 : .arg(settings.samplingWindow).arg(sampleRate);
327 352 : return false;
328 256 : }
329 1386 : settings.numberOfSamples = numberOfSamples; // Note the implicit uint64 to uint16 conversion.
330 768 : Q_ASSERT(settings.numberOfSamples * 1'000'000ull / settings.samplingWindow <= sampleRate); // Due to integer truncation.
331 768 : }
332 :
333 3234 : if (settings.samplingWindow == 0) {
334 2288 : qCDebug(lc).noquote() << tr("Calculating sampling-window for %Ln sample/s at %1Hz", nullptr,
335 0 : settings.numberOfSamples).arg(sampleRate);
336 1024 : Q_ASSERT(settings.numberOfSamples != 0);
337 1848 : settings.samplingWindow = settings.numberOfSamples * 1'000'000ull / sampleRate;
338 2288 : qCDebug(lc).noquote() << tr("Calculated %1us").arg(settings.samplingWindow);
339 1848 : if (settings.samplingWindow == 0) {
340 0 : qCCritical(lc).noquote() << tr("Failed to calculate a valid sampling window for a %L1 samples at %1Hz")
341 0 : .arg(settings.numberOfSamples).arg(sampleRate);
342 0 : return false;
343 0 : }
344 1024 : }
345 1792 : return true;
346 1792 : }
347 :
348 : /*!
349 : * \copybrief DeviceCommand::serviceDetailsDiscovered
350 : *
351 : * This override fetches the current device's status, and outputs it in the selected format.
352 : */
353 0 : void DsoCommand::serviceDetailsDiscovered()
354 0 : {
355 0 : DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
356 0 : settings.range = (minRangeFunc == nullptr) ? 0 : minRangeFunc(*service->pokitProduct(), rangeOptionValue);
357 0 : if (!configureWindow(*service->pokitProduct(), sampleRateValue, settings)) {
358 0 : disconnect(EXIT_FAILURE);
359 0 : return;
360 0 : }
361 0 : if (samplesValue == 0) samplesValue = settings.numberOfSamples;
362 0 : const QString range = service->toString(settings.range, settings.mode);
363 0 : const QString triggerInfo = (settings.command == DsoService::Command::FreeRunning) ? QString() :
364 0 : tr(", and a %1 at %2%3%4 (%5Hz)").arg(DsoService::toString(settings.command).toLower(),
365 0 : (settings.triggerLevel < 0.) ? u"-"_s : u""_s, appendSiPrefix(qAbs(settings.triggerLevel)),
366 0 : range.at(range.size()-1));
367 0 : qCInfo(lc).noquote() << tr("Sampling %1, with range %2, at %L3Hz (%Ln sample/s over %L4us)%5", nullptr, settings.numberOfSamples)
368 0 : .arg(DsoService::toString(settings.mode), (range.isNull()) ? QString::fromLatin1("N/A") : range)
369 0 : .arg(settings.numberOfSamples * 1'000'000ull / settings.samplingWindow).arg(settings.samplingWindow)
370 0 : .arg(triggerInfo);
371 0 : if (!service->enableMetadataNotifications()) {
372 0 : qCCritical(lc).noquote() << tr("Failed to enable metadata notifications");
373 0 : disconnect(EXIT_FAILURE);
374 0 : return;
375 0 : }
376 0 : if (!service->enableReadingNotifications()) {
377 0 : qCCritical(lc).noquote() << tr("Failed to enable reading notifications");
378 0 : disconnect(EXIT_FAILURE);
379 0 : return;
380 0 : }
381 0 : service->setSettings(settings);
382 0 : }
383 :
384 : /*!
385 : * \var DsoCommand::minRangeFunc
386 : *
387 : * Pointer to function for converting #rangeOptionValue to a Pokit device's range enumerator. This function pointer
388 : * is assigned during the command line parsing, but is not invoked until after the device's services are discovered,
389 : * because prior to that discovery, we don't know which product (Meter vs Pro vs Clamp, etc) we're talking to and thus
390 : * which enumerator list to be using.
391 : *
392 : * If the current mode does not support ranges (eg diode, and continuity modes), then this member will be \c nullptr.
393 : *
394 : * \see processOptions
395 : * \see serviceDetailsDiscovered
396 : */
397 :
398 : /*!
399 : * Invoked when the DSO settings have been written.
400 : */
401 0 : void DsoCommand::settingsWritten()
402 0 : {
403 0 : Q_ASSERT(service);
404 0 : qCDebug(lc).noquote() << tr("Settings written; DSO has started.");
405 0 : }
406 :
407 : /*!
408 : * Invoked when \a metadata has been received from the DSO.
409 : */
410 6283 : void DsoCommand::metadataRead(const DsoService::Metadata &data)
411 3488 : {
412 13126 : qCDebug(lc) << "status:" << (int)(data.status);
413 13126 : qCDebug(lc) << "scale:" << data.scale;
414 13126 : qCDebug(lc) << "mode:" << DsoService::toString(data.mode);
415 13126 : qCDebug(lc) << "range:" << service->toString(data.range, data.mode);
416 13126 : qCDebug(lc) << "samplingWindow:" << data.samplingWindow;
417 13126 : qCDebug(lc) << "numberOfSamples:" << data.numberOfSamples;
418 13126 : qCDebug(lc) << "samplingRate:" << data.samplingRate << "Hz";
419 9771 : this->metadata = data;
420 9771 : this->samplesToGo = data.numberOfSamples;
421 9771 : }
422 :
423 : /*!
424 : * Outputs DSO \a samples in the selected output format.
425 : */
426 7416 : void DsoCommand::outputSamples(const DsoService::Samples &samples)
427 4032 : {
428 9072 : QString unit;
429 11448 : switch (metadata.mode) {
430 4122 : case DsoService::Mode::DcVoltage: unit = u"Vdc"_s; break;
431 4122 : case DsoService::Mode::AcVoltage: unit = u"Vac"_s; break;
432 4122 : case DsoService::Mode::DcCurrent: unit = u"Adc"_s; break;
433 4122 : case DsoService::Mode::AcCurrent: unit = u"Aac"_s; break;
434 0 : default:
435 0 : qCDebug(lc).noquote() << tr(R"(No known unit for mode %1 "%2".)").arg((int)metadata.mode)
436 0 : .arg(DsoService::toString(metadata.mode));
437 4032 : }
438 13824 : const QString range = service->toString(metadata.range, metadata.mode);
439 :
440 60840 : for (const qint16 &sample: samples) {
441 53424 : static quint32 sampleNumber = 0; ++sampleNumber;
442 53424 : const float value = sample * metadata.scale;
443 53424 : switch (format) {
444 6272 : case OutputFormat::Csv:
445 20352 : for (; showCsvHeader; showCsvHeader = false) {
446 3664 : std::cout << qUtf8Printable(tr("sample_number,value,unit,range\n"));
447 896 : }
448 34272 : std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4\n")
449 6272 : .arg(sampleNumber).arg(value).arg(unit, range));
450 17808 : break;
451 17808 : case OutputFormat::Json:
452 66416 : std::cout << QJsonDocument(QJsonObject{
453 20496 : { u"value"_s, value },
454 20496 : { u"unit"_s, unit },
455 20496 : { u"range"_s, range },
456 29344 : { u"mode"_s, DsoService::toString(metadata.mode) },
457 73696 : }).toJson().toStdString();
458 17808 : break;
459 17808 : case OutputFormat::Text:
460 36512 : std::cout << qUtf8Printable(tr("%1 %2 %3\n").arg(sampleNumber).arg(value).arg(unit));
461 17808 : break;
462 18816 : }
463 53424 : --samplesToGo;
464 :
465 53424 : if ((sampleNumber > samplesValue) && (samplesValue != 0)) {
466 0 : qCInfo(lc).noquote() << tr("Finished fetching %Ln sample/s. Disconnecting.", nullptr, sampleNumber);
467 0 : if (device) disconnect(); // Will exit the application once disconnected.
468 0 : return;
469 0 : }
470 18816 : }
471 :
472 : // If we've received all the data for the current window, begin fetching another.
473 11448 : if (samplesToGo <= 0) {
474 15408 : qCDebug(lc).noquote() << tr("Finished fetching %Ln window sample/s (with %L2 to remaining).",
475 0 : nullptr, metadata.numberOfSamples).arg(samplesToGo);
476 11448 : if (settings.range != +PokitMeter::VoltageRange::AutoRange) service->setSettings(settings);
477 4032 : }
478 45160 : }
|