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 10448 : DsoCommand::DsoCommand(QObject * const parent) : DeviceCommand(parent)
26 3096 : {
27 :
28 10516 : }
29 :
30 11970 : QStringList DsoCommand::requiredOptions(const QCommandLineParser &parser) const
31 4701 : {
32 59934 : return DeviceCommand::requiredOptions(parser) + QStringList{
33 4701 : u"mode"_s,
34 4701 : u"range"_s,
35 46767 : };
36 4701 : }
37 :
38 5950 : QStringList DsoCommand::supportedOptions(const QCommandLineParser &parser) const
39 2295 : {
40 52870 : return DeviceCommand::supportedOptions(parser) + QStringList{
41 2295 : u"interval"_s,
42 2295 : u"samples"_s,
43 2295 : u"sample-rate"_s,
44 2295 : u"trigger-level"_s,
45 2295 : u"trigger-mode"_s,
46 2295 : u"window-size"_s,
47 47005 : };
48 2295 : }
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 5880 : QStringList DsoCommand::processOptions(const QCommandLineParser &parser)
57 2184 : {
58 8064 : QStringList errors = DeviceCommand::processOptions(parser);
59 8064 : if (!errors.isEmpty()) {
60 156 : return errors;
61 156 : }
62 :
63 : // Parse the (required) mode option.
64 13104 : if (const QString mode = parser.value(u"mode"_s).trimmed().toLower();
65 16536 : mode.startsWith(u"ac v"_s) || mode.startsWith(u"vac"_s)) {
66 192 : settings.mode = DsoService::Mode::AcVoltage;
67 16112 : } else if (mode.startsWith(u"dc v"_s) || mode.startsWith(u"vdc"_s)) {
68 6720 : settings.mode = DsoService::Mode::DcVoltage;
69 2936 : } else if (mode.startsWith(u"ac c"_s) || mode.startsWith(u"aac"_s)) {
70 192 : settings.mode = DsoService::Mode::AcCurrent;
71 848 : } else if (mode.startsWith(u"dc c"_s) || mode.startsWith(u"adc"_s)) {
72 192 : settings.mode = DsoService::Mode::DcCurrent;
73 52 : } else {
74 368 : errors.append(tr("Unknown DSO mode: %1").arg(parser.value(u"mode"_s)));
75 52 : return errors;
76 1924 : }
77 :
78 : // Parse the (required) range option.
79 5472 : QString unit;
80 1976 : {
81 7296 : const QString value = parser.value(u"range"_s);
82 1976 : quint32 sensibleMinimum = 0;
83 7296 : 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 6860 : case DsoService::Mode::DcVoltage:
88 1872 : case DsoService::Mode::AcVoltage:
89 6912 : minRangeFunc = minVoltageRange;
90 6912 : unit = u"V"_s;
91 1872 : sensibleMinimum = 50; // mV.
92 6912 : break;
93 332 : case DsoService::Mode::DcCurrent:
94 104 : case DsoService::Mode::AcCurrent:
95 384 : minRangeFunc = minCurrentRange;
96 384 : unit = u"A"_s;
97 104 : sensibleMinimum = 5; // mA.
98 384 : break;
99 1976 : }
100 1976 : Q_ASSERT(!unit.isEmpty());
101 7296 : rangeOptionValue = parseNumber<std::milli>(value, unit, sensibleMinimum);
102 7296 : if (rangeOptionValue == 0) {
103 284 : errors.append(tr("Invalid range value: %1").arg(value));
104 52 : }
105 3800 : }
106 :
107 : // Parse the trigger-level option.
108 10792 : if (parser.isSet(u"trigger-level"_s)) {
109 416 : float sign = 1.0;
110 2416 : const QString rawValue = parser.value(u"trigger-level"_s);
111 416 : QString absValue = rawValue;
112 416 : DOKIT_STRING_INDEX_TYPE nonSpacePos;
113 1536 : for (nonSpacePos = 0; (nonSpacePos < rawValue.length()) && (rawValue.at(nonSpacePos) == u' '); ++nonSpacePos);
114 1536 : if ((nonSpacePos < rawValue.length()) && (rawValue.at(nonSpacePos) == u'-')) {
115 0 : absValue = rawValue.mid(nonSpacePos+1);
116 0 : sign = -1.0;
117 0 : }
118 1536 : const float level = parseNumber<std::ratio<1>,float>(absValue, unit, 0.f);
119 2080 : qCDebug(lc) << "Trigger level" << rawValue << absValue << nonSpacePos << sign << level;
120 1536 : if (qIsNaN(level)) {
121 284 : errors.append(tr("Invalid trigger-level value: %1").arg(rawValue));
122 364 : } else {
123 1344 : settings.triggerLevel = sign * level;
124 1820 : qCDebug(lc) << "Trigger level" << settings.triggerLevel;
125 : // Check the trigger level is within the Votage / Current range.
126 2324 : 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 364 : }
131 800 : }
132 :
133 : // Parse the trigger-mode option.
134 10792 : if (parser.isSet(u"trigger-mode"_s)) {
135 2656 : const QString triggerMode = parser.value(u"trigger-mode"_s).trimmed().toLower();
136 2272 : if (triggerMode.startsWith(u"free"_s)) {
137 576 : settings.command = DsoService::Command::FreeRunning;
138 1420 : } else if (triggerMode.startsWith(u"ris"_s)) {
139 384 : settings.command = DsoService::Command::RisingEdgeTrigger;
140 852 : } else if (triggerMode.startsWith(u"fall"_s)) {
141 384 : settings.command = DsoService::Command::FallingEdgeTrigger;
142 104 : } else {
143 368 : errors.append(tr("Unknown trigger mode: %1").arg(parser.value(u"trigger-mode"_s)));
144 52 : }
145 800 : }
146 :
147 : // Ensure that if either trigger option is present, then both are.
148 16112 : if (parser.isSet(u"trigger-level"_s) != parser.isSet(u"trigger-mode"_s)) {
149 384 : errors.append(tr("If either option is provided, then both must be: trigger-level, trigger-mode"));
150 104 : }
151 :
152 : // Parse the sample-rate option.
153 10792 : if (parser.isSet(u"sample-rate"_s)) {
154 10872 : const QString value = parser.value(u"sample-rate"_s);
155 6912 : sampleRateValue = parseNumber<std::ratio<1,1>>(value, u"Hz"_s, (quint32)50'000);
156 6912 : if (sampleRateValue == 0) {
157 568 : errors.append(tr("Invalid sample-rate value: %1").arg(value));
158 6528 : } else if (sampleRateValue > 1'000'000) {
159 492 : qCWarning(lc).noquote() << tr("Pokit devices do not officially support sample rates greater than 1Mhz");
160 52 : }
161 3600 : }
162 :
163 : // Parse the interval option.
164 10792 : if (parser.isSet(u"interval"_s)) {
165 4530 : const QString value = parser.value(u"interval"_s);
166 2880 : const quint32 interval = parseNumber<std::micro>(value, u"s"_s, (quint32)500'000);
167 2880 : if (interval == 0) {
168 568 : errors.append(tr("Invalid interval value: %1").arg(value));
169 676 : } else {
170 2496 : settings.samplingWindow = interval;
171 676 : }
172 1500 : }
173 :
174 : // Parse the window-size option.
175 10792 : if (parser.isSet(u"window-size"_s)) {
176 2416 : const QString value = parser.value(u"window-size"_s);
177 1536 : const quint32 samples = parseNumber<std::ratio<1>>(value, u"S"_s);
178 1536 : if (samples == 0) {
179 568 : errors.append(tr("Invalid window-size value: %1").arg(value));
180 1152 : } 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 312 : } else {
184 1152 : settings.numberOfSamples = (quint16)samples;
185 1152 : if (const auto maxSamples = maxWindowSize(PokitProduct::PokitPro); settings.numberOfSamples > maxSamples) {
186 400 : qCWarning(lc).noquote() <<
187 284 : tr("No Pokit device officially supports windows greater than %L1 samples").arg(maxSamples);
188 52 : }
189 312 : }
190 800 : }
191 :
192 : // Ensure that we have at least: sample-rate, or both interval and window-size.
193 7992 : if ((!parser.isSet(u"sample-rate"_s)) && !(parser.isSet(u"interval"_s) && parser.isSet(u"window-size"_s))) {
194 192 : errors.append(tr("Missing required option/s: either sample-rate, or both interval and window-size"));
195 52 : }
196 :
197 : // If we have all three sample-rate related options, ensure they agree.
198 7296 : if ((sampleRateValue != 0) && (settings.numberOfSamples != 0) && (settings.samplingWindow != 0)) {
199 384 : const quint32 sampleRate = settings.numberOfSamples * 1'000'000ull / settings.samplingWindow;
200 384 : if (sampleRate != sampleRateValue) {
201 240 : errors.append(tr("Windows size (%1 samples) and interval (%2ns) yield a sample rate of %3Hz, which does "
202 52 : "not match the supplied sample-rate (%4Hz). Tip: leave one option unset to have dokit calculate the "
203 266 : "remaining option.").arg(settings.numberOfSamples).arg(settings.samplingWindow).arg(sampleRate)
204 314 : .arg(sampleRateValue));
205 52 : }
206 104 : }
207 :
208 : // Parse the samples option.
209 10792 : if (parser.isSet(u"samples"_s)) {
210 906 : const QString value = parser.value(u"samples"_s);
211 576 : samplesValue = parseNumber<std::ratio<1>>(value, u"S"_s);
212 576 : if (samplesValue == 0) {
213 568 : errors.append(tr("Invalid samples value: %1").arg(value));
214 104 : }
215 300 : }
216 1976 : return errors;
217 3800 : }
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 1420 : quint16 DsoCommand::maxWindowSize(const PokitProduct product)
244 2754 : {
245 4174 : switch (product) {
246 1221 : case PokitProduct::PokitMeter:
247 1221 : return 8'191;
248 2783 : case PokitProduct::PokitPro:
249 2783 : return 16'384;
250 2754 : }
251 1122 : Q_ASSERT_X(false, "DsoCommand::maxWindowSize", "Unknown PokitProduct enum value");
252 0 : return 0;
253 2754 : }
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 1400 : bool DsoCommand::configureWindow(const PokitProduct product, const quint32 sampleRate, DsoService::Settings &settings)
263 2220 : {
264 2220 : const quint32 maxSampleRate = 1'000'000; // Pokit Meter and Pokit Pro both sample up to 1MHz.
265 3620 : const quint32 maxWindowSize = DsoCommand::maxWindowSize(product);
266 :
267 3620 : 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 3620 : 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 3620 : if ((settings.numberOfSamples != 0) && (settings.samplingWindow != 0)) {
280 860 : qCDebug(lc).noquote() << "Both numberOfSamples and samplingWindow are set, so no need to derive either";
281 724 : if (sampleRate != 0) {
282 724 : const quint32 derivedRate = settings.numberOfSamples * 1'000'000ull / settings.samplingWindow;
283 860 : qCDebug(lc).noquote() << "derivedRate" << derivedRate << sampleRate;
284 724 : if (sampleRate != derivedRate) {
285 662 : qCWarning(lc).noquote() << tr("Ignoring sample-rate, as interval and window-size both provided");
286 222 : }
287 444 : }
288 724 : return true; // Nothing more to do.
289 444 : }
290 1980 : 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 2896 : if ((settings.numberOfSamples == 0) && (settings.samplingWindow == 0)) {
294 1290 : qCDebug(lc).noquote() << tr("Choosing best number-of-samples for sample-rate %2Hz").arg(sampleRate);
295 666 : double smallestDifference = std::numeric_limits<double>::quiet_NaN();
296 13345311 : for (quint32 windowSize = maxWindowSize; windowSize > 0; --windowSize) {
297 13344225 : const quint32 period = windowSize * 1'000'000ull / sampleRate;
298 13344225 : const double effectiveRate = double(windowSize) * 1'000'000.0 / (double)period;
299 13344225 : if (effectiveRate > maxSampleRate) continue; // Skip sizes that would exceed the device's max sample rate.
300 13344225 : if (const quint32 effectivePeriod = windowSize * 1'000'000ull / sampleRate;
301 9903305 : effectivePeriod > 1'000'000) continue; // Skip sizes that would take longer than 1s to fetch.
302 8897236 : 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 8897236 : if ((settings.numberOfSamples == 0) || (difference < smallestDifference)) {
306 3258 : settings.numberOfSamples = windowSize;
307 1998 : smallestDifference = difference;
308 1998 : }
309 5456316 : }
310 1290 : qCDebug(lc).noquote() << tr("Chose %Ln sample/s, with error ±%2Hz", nullptr,
311 0 : settings.numberOfSamples).arg(smallestDifference, 0, 'f');
312 1086 : 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 666 : }
317 :
318 2896 : if (settings.numberOfSamples == 0) {
319 1720 : qCDebug(lc).noquote() << tr("Calculating number-of-samples for %1us window at %2Hz")
320 0 : .arg(settings.samplingWindow).arg(sampleRate);
321 888 : Q_ASSERT(settings.samplingWindow != 0);
322 1448 : const auto numberOfSamples = sampleRate * settings.samplingWindow / 1'000'000ull;
323 1720 : qCDebug(lc).noquote() << tr("Calculated %Ln sample/s", nullptr, numberOfSamples);
324 1448 : if ((numberOfSamples == 0) || (numberOfSamples > maxWindowSize)) {
325 600 : qCCritical(lc).noquote() << tr("Failed to calculate a valid number of samples for a %L1us period at %2Hz")
326 484 : .arg(settings.samplingWindow).arg(sampleRate);
327 294 : return false;
328 222 : }
329 1086 : settings.numberOfSamples = numberOfSamples; // Note the implicit uint64 to uint16 conversion.
330 666 : Q_ASSERT(settings.numberOfSamples * 1'000'000ull / settings.samplingWindow <= sampleRate); // Due to integer truncation.
331 666 : }
332 :
333 2534 : if (settings.samplingWindow == 0) {
334 1720 : qCDebug(lc).noquote() << tr("Calculating sampling-window for %Ln sample/s at %1Hz", nullptr,
335 0 : settings.numberOfSamples).arg(sampleRate);
336 888 : Q_ASSERT(settings.numberOfSamples != 0);
337 1448 : settings.samplingWindow = settings.numberOfSamples * 1'000'000ull / sampleRate;
338 1720 : qCDebug(lc).noquote() << tr("Calculated %1us").arg(settings.samplingWindow);
339 1448 : 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 888 : }
345 1554 : return true;
346 1554 : }
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 4270 : void DsoCommand::metadataRead(const DsoService::Metadata &data)
411 1671 : {
412 8015 : qCDebug(lc) << "status:" << (int)(data.status);
413 8015 : qCDebug(lc) << "scale:" << data.scale;
414 8015 : qCDebug(lc) << "mode:" << DsoService::toString(data.mode);
415 8015 : qCDebug(lc) << "range:" << service->toString(data.range, data.mode);
416 8015 : qCDebug(lc) << "samplingWindow:" << data.samplingWindow;
417 8015 : qCDebug(lc) << "numberOfSamples:" << data.numberOfSamples;
418 8015 : qCDebug(lc) << "samplingRate:" << data.samplingRate << "Hz";
419 5941 : this->metadata = data;
420 5941 : this->samplesToGo = data.numberOfSamples;
421 5941 : }
422 :
423 : /*!
424 : * Outputs DSO \a samples in the selected output format.
425 : */
426 5040 : void DsoCommand::outputSamples(const DsoService::Samples &samples)
427 1872 : {
428 5184 : QString unit;
429 6912 : switch (metadata.mode) {
430 2556 : case DsoService::Mode::DcVoltage: unit = u"Vdc"_s; break;
431 2556 : case DsoService::Mode::AcVoltage: unit = u"Vac"_s; break;
432 2556 : case DsoService::Mode::DcCurrent: unit = u"Adc"_s; break;
433 2556 : 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 1872 : }
438 8640 : const QString range = service->toString(metadata.range, metadata.mode);
439 :
440 37296 : for (const qint16 &sample: samples) {
441 32256 : static quint32 sampleNumber = 0; ++sampleNumber;
442 32256 : const float value = sample * metadata.scale;
443 32256 : switch (format) {
444 2912 : case OutputFormat::Csv:
445 12288 : for (; showCsvHeader; showCsvHeader = false) {
446 2272 : std::cout << qUtf8Printable(tr("sample_number,value,unit,range\n"));
447 416 : }
448 22400 : std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4\n")
449 2912 : .arg(sampleNumber).arg(value).arg(unit, range));
450 10752 : break;
451 10752 : case OutputFormat::Json:
452 56224 : std::cout << QJsonDocument(QJsonObject{
453 12432 : { u"value"_s, value },
454 12432 : { u"unit"_s, unit },
455 12432 : { u"range"_s, range },
456 18592 : { u"mode"_s, DsoService::toString(metadata.mode) },
457 47600 : }).toJson().toStdString();
458 10752 : break;
459 10752 : case OutputFormat::Text:
460 23296 : std::cout << qUtf8Printable(tr("%1 %2 %3\n").arg(sampleNumber).arg(value).arg(unit));
461 10752 : break;
462 8736 : }
463 32256 : --samplesToGo;
464 :
465 32256 : 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 8736 : }
471 :
472 : // If we've received all the data for the current window, begin fetching another.
473 6912 : if (samplesToGo <= 0) {
474 9360 : qCDebug(lc).noquote() << tr("Finished fetching %Ln window sample/s (with %L2 to remaining).",
475 0 : nullptr, metadata.numberOfSamples).arg(samplesToGo);
476 6912 : if (settings.range != +PokitMeter::VoltageRange::AutoRange) service->setSettings(settings);
477 1872 : }
478 28016 : }
|